TCX importing and Pedal Force vs. Pedal Velocity Chart from

J.T. Conklin (jtc@acorntoolworks.com).
This commit is contained in:
Sean C. Rhea
2008-05-04 06:50:34 +00:00
parent aed29e150d
commit f27ff27c3b
10 changed files with 597 additions and 5 deletions

View File

@@ -24,6 +24,7 @@
#include "ChooseCyclistDialog.h"
#include "ConfigDialog.h"
#include "CpintPlot.h"
#include "PfPvPlot.h"
#include "DownloadRideDialog.h"
#include "PowerHist.h"
#include "RideItem.h"
@@ -49,7 +50,7 @@
#define MILES_PER_KM 0.62137119
static char *rideFileRegExp = ("^(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)"
"_(\\d\\d)_(\\d\\d)_(\\d\\d)\\.(raw|srm|csv)$");
"_(\\d\\d)_(\\d\\d)_(\\d\\d)\\.(raw|srm|csv|tcx)$");
QString
MainWindow::notesFileName(QString rideFileName) {
@@ -281,6 +282,33 @@ MainWindow::MainWindow(const QDir &home) :
tabWidget->addTab(window, "Power Histogram");
//////////////////////// Pedal Force/Velocity Plot ////////////////////////
window = new QWidget;
vlayout = new QVBoxLayout;
QHBoxLayout *qaLayout = new QHBoxLayout;
pfPvPlot = new PfPvPlot();
QLabel *qaCPLabel = new QLabel(tr("CP:"), window);
qaCPValue = new QLineEdit(QString("%1").arg(pfPvPlot->getCP()));
QLabel *qaCadLabel = new QLabel(tr("Cadence:"), window);
qaCadValue = new QLineEdit(QString("%1").arg(pfPvPlot->getCAD()));
QLabel *qaClLabel = new QLabel(tr("Crank Length:"), window);
qaClValue = new QLineEdit(QString("%1").arg(pfPvPlot->getCL()));
qaLayout->addWidget(qaCPLabel);
qaLayout->addWidget(qaCPValue);
qaLayout->addWidget(qaCadLabel);
qaLayout->addWidget(qaCadValue);
qaLayout->addWidget(qaClLabel);
qaLayout->addWidget(qaClValue);
vlayout->addWidget(pfPvPlot);
vlayout->addLayout(qaLayout);
window->setLayout(vlayout);
window->show();
tabWidget->addTab(window, tr("PF/PV Plot"));
//////////////////////// Ride Notes ////////////////////////
rideNotes = new QTextEdit;
@@ -316,6 +344,12 @@ MainWindow::MainWindow(const QDir &home) :
this, SLOT(setBinWidthFromSlider()));
connect(binWidthLineEdit, SIGNAL(returnPressed()),
this, SLOT(setBinWidthFromLineEdit()));
connect(qaCPValue, SIGNAL(returnPressed()),
this, SLOT(setQaCPFromLineEdit()));
connect(qaCadValue, SIGNAL(returnPressed()),
this, SLOT(setQaCADFromLineEdit()));
connect(qaClValue, SIGNAL(returnPressed()),
this, SLOT(setQaCLFromLineEdit()));
connect(tabWidget, SIGNAL(currentChanged(int)),
this, SLOT(tabChanged(int)));
connect(rideNotes, SIGNAL(textChanged()),
@@ -340,6 +374,8 @@ MainWindow::MainWindow(const QDir &home) :
SLOT(importSRM()), tr("Ctrl+I"));
rideMenu->addAction(tr("&Import from CSV..."), this,
SLOT (importCSV()), tr ("Ctrl+S"));
rideMenu->addAction(tr("&Import from TCX..."), this,
SLOT (importTCX()));
rideMenu->addAction(tr("Find &best intervals..."), this,
SLOT(findBestIntervals()), tr ("Ctrl+B"));
QMenu *optionsMenu = menuBar()->addMenu(tr("&Options"));
@@ -595,6 +631,54 @@ MainWindow::importSRM()
}
}
void
MainWindow::importTCX()
{
QStringList fileNames = QFileDialog::getOpenFileNames(
this, tr("Import SRM"), QDir::homePath(),
tr("TCX Format (*.tcx)"));
QStringListIterator i(fileNames);
while (i.hasNext()) {
QString fileName = i.next();
QFile file(fileName);
QStringList errors;
RideFile *ride =
RideFileFactory::instance().openRideFile(file, errors);
if (!ride || !errors.empty()) {
QString all = (ride
? tr("Non-fatal problem(s) opening %1:")
: tr("Fatal problem(s) opening %1:")).arg(fileName);
QStringListIterator i(errors);
while (i.hasNext())
all += "\n" + i.next();
if (ride)
QMessageBox::warning(this, tr("Open Warning"), all);
else {
QMessageBox::critical(this, tr("Open Error"), all);
return;
}
}
QChar zero = QLatin1Char('0');
QString name = QString("%1_%2_%3_%4_%5_%6.tcx")
.arg(ride->startTime().date().year(), 4, 10, zero)
.arg(ride->startTime().date().month(), 2, 10, zero)
.arg(ride->startTime().date().day(), 2, 10, zero)
.arg(ride->startTime().time().hour(), 2, 10, zero)
.arg(ride->startTime().time().minute(), 2, 10, zero)
.arg(ride->startTime().time().second(), 2, 10, zero);
if (!file.copy(home.absolutePath() + "/" + name)) {
QMessageBox::critical(this, tr("Copy Error"),
tr("Couldn't copy %1").arg(fileName));
return;
}
delete ride;
addRide(name);
}
}
void
MainWindow::findBestIntervals()
{
@@ -617,6 +701,8 @@ MainWindow::rideSelected()
cpintPlot->calculate(ride->fileName, ride->dateTime);
if (ride->ride)
powerHist->setData(ride->ride);
if (ride->ride)
pfPvPlot->setData(ride->ride);
QDate wstart = ride->dateTime.date();
wstart = wstart.addDays(Qt::Monday - wstart.dayOfWeek());
@@ -875,6 +961,27 @@ MainWindow::setBinWidthFromLineEdit()
}
}
void
MainWindow::setQaCPFromLineEdit()
{
int value = qaCPValue->text().toInt();
pfPvPlot->setCP(value);
}
void
MainWindow::setQaCADFromLineEdit()
{
int value = qaCadValue->text().toInt();
pfPvPlot->setCAD(value);
}
void
MainWindow::setQaCLFromLineEdit()
{
double value = qaClValue->text().toDouble();
pfPvPlot->setCL(value);
}
void
MainWindow::tabChanged(int index)
{

View File

@@ -26,6 +26,7 @@
class AllPlot;
class CpintPlot;
class PfPvPlot;
class PowerHist;
class QwtPlotPanner;
class QwtPlotPicker;
@@ -56,11 +57,15 @@ class MainWindow : public QMainWindow
void exportCSV();
void importCSV();
void importSRM();
void importTCX();
void findBestIntervals();
void setSmoothingFromSlider();
void setSmoothingFromLineEdit();
void setBinWidthFromSlider();
void setBinWidthFromLineEdit();
void setQaCPFromLineEdit();
void setQaCADFromLineEdit();
void setQaCLFromLineEdit();
void tabChanged(int index);
void pickerMoved(const QPoint &);
void aboutDialog();
@@ -97,6 +102,12 @@ class MainWindow : public QMainWindow
PowerHist *powerHist;
Zones *zones;
// pedal force/pedal velocity scatter plot widgets
PfPvPlot *pfPvPlot;
QLineEdit *qaCPValue;
QLineEdit *qaCadValue;
QLineEdit *qaClValue;
QTextEdit *rideNotes;
QString currentNotesFile;
bool currentNotesChanged;

165
src/gui/PfPvPlot.cpp Normal file
View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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 "PfPvPlot.h"
#include "RideFile.h"
#include "Settings.h"
#include <assert.h>
#include <qwt_data.h>
#include <qwt_legend.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_marker.h>
#include <set>
#define PI 3.14159
PfPvPlot::PfPvPlot()
: cp_ (300),
cad_ (85),
cl_ (0.175)
{
setCanvasBackground(Qt::white);
setAxisTitle(yLeft, "Average Effective Pedal Force (N)");
setAxisScale(yLeft, 0, 600);
setAxisTitle(xBottom, "Circumferential Pedal Velocity (m/s)");
setAxisScale(xBottom, 0, 3);
mX = new QwtPlotMarker();
mX->setLineStyle(QwtPlotMarker::VLine);
mX->attach(this);
mY = new QwtPlotMarker();
mY->setLineStyle(QwtPlotMarker::HLine);
mY->attach(this);
cpCurve = new QwtPlotCurve();
cpCurve->setRenderHint(QwtPlotItem::RenderAntialiased);
cpCurve->attach(this);
curve = new QwtPlotCurve();
curve->setPen(QPen(Qt::red));
curve->setStyle(QwtPlotCurve::Dots);
curve->attach(this);
recalc();
}
void
PfPvPlot::setData(RideFile *ride)
{
setTitle(ride->startTime().toString(GC_DATETIME_FORMAT));
// due to the discrete power and cadence values returned by the
// power meter, there will very likely be many duplicate values.
// Rather than pass them all to the curve, use a set to strip
// out duplicates.
std::set<std::pair<double, double> > dataSet;
QListIterator<RideFilePoint*> i(ride->dataPoints());
while (i.hasNext()) {
const RideFilePoint *p1 = i.next();
if (p1->watts != 0 && p1->cad != 0) {
double aepf = (p1->watts * 60.0) / (p1->cad * cl_ * 2.0 * PI);
double cpv = (p1->cad * cl_ * 2.0 * PI) / 60.0;
dataSet.insert(std::make_pair<double, double>(aepf, cpv));
}
}
// Now that we have the set of points, transform them into the
// QwtArrays needed to set the curve's data.
QwtArray<double> aepfArray;
QwtArray<double> cpvArray;
std::set<std::pair<double, double> >::const_iterator j(dataSet.begin());
while (j != dataSet.end()) {
const std::pair<double, double>& dataPoint = *j;
aepfArray.push_back(dataPoint.first);
cpvArray.push_back(dataPoint.second);
++j;
}
curve->setData(cpvArray, aepfArray);
replot();
}
void
PfPvPlot::recalc()
{
double cpv = (cad_ * cl_ * 2.0 * PI) / 60.0;
mX->setXValue(cpv);
double aepf = (cp_ * 60.0) / (cad_ * cl_ * 2.0 * PI);
mY->setYValue(aepf);
QwtArray<double> aepfArray;
QwtArray<double> cpvArray;
for (double cpv = 0.1; cpv <= 3.0; cpv += 0.05) {
double aepf = cp_ / cpv;
aepfArray.push_back(aepf);
cpvArray.push_back(cpv);
}
cpCurve->setData(cpvArray, aepfArray);
replot();
}
int
PfPvPlot::getCP()
{
return cp_;
}
void
PfPvPlot::setCP(int cp)
{
cp_ = cp;
recalc();
}
int
PfPvPlot::getCAD()
{
return cad_;
}
void
PfPvPlot::setCAD(int cadence)
{
cad_ = cadence;
recalc();
}
double
PfPvPlot::getCL()
{
return cl_;
}
void
PfPvPlot::setCL(double cranklen)
{
cl_ = cranklen;
recalc();
}

62
src/gui/PfPvPlot.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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
*/
#ifndef _GC_QaPlot_h
#define _GC_QaPlot_h 1
#include <qwt_plot.h>
// forward references
class RideFile;
class QwtPlotCurve;
class QwtPlotMarker;
class PfPvPlot : public QwtPlot
{
Q_OBJECT
public:
PfPvPlot();
void setData(RideFile *ride);
int getCP();
void setCP(int cp);
int getCAD();
void setCAD(int cadence);
double getCL();
void setCL(double cranklen);
public slots:
protected:
void recalc();
QwtPlotCurve *curve;
QwtPlotCurve *cpCurve;
QwtPlotMarker *mX;
QwtPlotMarker *mY;
int cp_;
int cad_;
double cl_;
};
#endif // _GC_QaPlot_h

114
src/gui/TcxParser.cpp Normal file
View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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 <QString>
#include "TcxParser.h"
TcxParser::TcxParser (RideFile* rideFile)
: rideFile_(rideFile)
{
}
bool
TcxParser::startElement( const QString&, const QString&,
const QString& qName,
const QXmlAttributes& qAttributes)
{
buf_.clear();
if (qName == "Activity") {
lap_ = 0;
} else if (qName == "Lap") {
// Use the time of the first lap as the time of the activity.
if (lap_ == 0) {
start_time_ = QDateTime::fromString(qAttributes.value("StartTime"),
Qt::ISODate);
rideFile_->setStartTime(start_time_);
last_km_ = 0.0;
last_time_ = start_time_;
}
++lap_;
} else if (qName == "Trackpoint") {
watts_ = 0.0;
cad_ = 0.0;
hr_ = 0.0;
}
return TRUE;
}
bool
TcxParser::endElement( const QString&, const QString&, const QString& qName)
{
if (qName == "Time") {
time_ = QDateTime::fromString(buf_, Qt::ISODate);
}
if (qName == "DistanceMeters") {
km_ = buf_.toDouble() / 1000;
} else if (qName == "Watts") {
watts_ = buf_.toDouble();
} else if (qName == "Value") {
hr_ = buf_.toDouble();
} else if (qName == "Cadence") {
cad_ = buf_.toDouble();
} else if (qName == "Trackpoint") {
// compute the elapsed time and distance traveled since the
// last recorded trackpoint
double delta_d = km_ - last_km_;
double delta_t = last_time_.secsTo(time_);
// compute speed for this trackpoint by dividing the distance
// traveled by the elapsed time. The elapsed time will be 0.0
// for the first trackpoint -- so set speed to 0.0 instead of
// dividing by zero.
double kph = ((delta_t != 0.0) ? ((delta_d / delta_t) * 3600.0) : 0.0);
// Don't know what to do about torque
double nm = 0.0;
// Time from beginning of activity
double secs = start_time_.secsTo(time_);
// Work around bug in 705 firmware where cadence and
// power values repeat when stopped.
if (delta_d == 0.0) {
watts_ = 0.0;
cad_ = 0.0;
}
// Record trackpoint
rideFile_->appendPoint(secs, cad_, hr_, km_,
kph, nm, watts_, lap_);
last_km_ = km_;
last_time_ = time_;
}
return TRUE;
}
bool
TcxParser::characters( const QString& str )
{
buf_ += str;
return TRUE;
}

58
src/gui/TcxParser.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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
*/
#ifndef _TcxParser_h
#define _TcxParser_h
#include "RideFile.h"
#include <QString>
#include <QDateTime>
#include <QXmlDefaultHandler>
class TcxParser : public QXmlDefaultHandler
{
public:
TcxParser(RideFile* rideFile);
bool startElement( const QString&, const QString&, const QString&,
const QXmlAttributes& );
bool endElement( const QString&, const QString&, const QString& );
bool characters( const QString& );
private:
RideFile* rideFile_;
QString buf_;
QDateTime start_time_;
QDateTime last_time_;
QDateTime time_;
double last_km_;
double km_;
int lap_;
double watts_;
double cad_;
double hr_;
};
#endif // _TcxParser_h

40
src/gui/TcxRideFile.cpp Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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 "TcxRideFile.h"
#include "TcxParser.h"
static int tcxFileReaderRegistered =
RideFileFactory::instance().registerReader("tcx", new TcxFileReader());
RideFile *TcxFileReader::openRideFile(QFile &file, QStringList &errors) const
{
(void) errors;
RideFile *rideFile = new RideFile();
rideFile->setRecIntSecs(1.0);
TcxParser handler(rideFile);
QXmlInputSource source (&file);
QXmlSimpleReader reader;
reader.setContentHandler (&handler);
reader.parse (source);
return rideFile;
}

30
src/gui/TcxRideFile.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net),
* J.T Conklin (jtc@acorntoolworks.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
*/
#ifndef _TcxRideFile_h
#define _TcxRideFile_h
#include "RideFile.h"
struct TcxFileReader : public RideFileReader {
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
};
#endif // _TcxRideFile_h

View File

@@ -51,7 +51,7 @@ cpi_files_to_update(const char *dir)
cpi_file_info *head = NULL, *tail = NULL;
if (regcomp(&reg, "^([0-9][0-9][0-9][0-9])_([0-9][0-9])_([0-9][0-9])"
"_([0-9][0-9])_([0-9][0-9])_([0-9][0-9])\\.(raw|srm|csv)$",
"_([0-9][0-9])_([0-9][0-9])_([0-9][0-9])\\.(raw|srm|csv|tcx)$",
REG_EXTENDED))
assert(0);

View File

@@ -37,11 +37,14 @@ HEADERS += \
RideItem.h \
RideMetric.h \
SrmRideFile.h \
TcxParser.h \
TcxRideFile.h \
TimeUtils.h \
Zones.h \
Pages.h \
ConfigDialog.h \
cpint.h
cpint.h \
PfPvPlot.h
SOURCES += \
AllPlot.cpp \
@@ -62,12 +65,14 @@ SOURCES += \
RideItem.cpp \
RideMetric.cpp \
SrmRideFile.cpp \
TimeUtils.cpp \
TcxParser.cpp \
TcxRideFile.cpp \
TimeUtils.cpp \
Zones.cpp \
Pages.cpp \
ConfigDialog.cpp \
cpint.cpp \
\
PfPvPlot.cpp \
main.cpp
RESOURCES = application.qrc