Skip to content

Commit

Permalink
Add support for pool swimming tcx files from Garmin Connect
Browse files Browse the repository at this point in the history
When Sport="Other", there is no GPS data but there is distance information
it is assumed the file cames from lap swimming.
They have one Trackpoint per length, which are expanded to one point per second
in similar way to Smart Recording and laps without Trackpoints to signal pauses
which are expanded to one point per second up to 10x Smart Recording HWM.
Added contributed Garmin FR910xt pool swim file to test/rides folder
  • Loading branch information
amtriathlon committed Feb 19, 2015
1 parent e0688da commit ea37db2
Show file tree
Hide file tree
Showing 3 changed files with 931 additions and 4 deletions.
47 changes: 43 additions & 4 deletions src/TcxParser.cpp
Expand Up @@ -64,9 +64,11 @@ TcxParser::startElement( const QString&, const QString&, const QString& qName, c
if (rides) rides->append(rideFile);

// Sport ("Biking", "Running", "Other")
swim = NotSwim;
QString sport = qAttributes.value("Sport");
if (sport == "Biking") rideFile->setTag("Sport", "Bike");
else if (sport == "Running") rideFile->setTag("Sport", "Run");
else if (sport == "Other") swim = MayBeSwim;

} else if (qName == "Lap") {

Expand Down Expand Up @@ -110,6 +112,7 @@ TcxParser::endElement( const QString&, const QString&, const QString& qName)
secs = start_time.secsTo(time);

} else if (qName == "DistanceMeters") { distance = buffer.toDouble() / 1000; }
else if (qName == "TotalTimeSeconds") { lapSecs = buffer.toDouble(); }
else if (qName == "Watts" || qName.endsWith( ":Watts")) { power = buffer.toDouble(); } //TCX Extension Fields may use a namespace prefix
else if (qName == "Speed" || qName.endsWith(":Speed")) { speed = buffer.toDouble() * 3.6; } //TCX Extension Fields may use a namespace prefix
else if (qName == "RunCadence" || qName.endsWith( ":RunCadence")) { rcad = buffer.toDouble(); } //TCX Extension Fields may use a namespace prefix
Expand Down Expand Up @@ -164,6 +167,13 @@ TcxParser::endElement( const QString&, const QString&, const QString& qName)

if (lat == 0 && lon == 0) badgps = true;

// If sport was Other and we have distance but no GPS data
// we assume it is a pool swimming activity
if (swim == MayBeSwim && badgps && distance > 0) {
swim = Swim;
rideFile->setTag("Sport", "Swim");
}

// for smart recording, the delta_t will not be constant
// average all the calculations based on the previous
// point.
Expand Down Expand Up @@ -196,7 +206,7 @@ TcxParser::endElement( const QString&, const QString&, const QString& qName)

if (prevPoint->lat == 0 && prevPoint->lon == 0) badgps = true;
// Smart Recording High Water Mark.
if ((isGarminSmartRecording.toInt() == 0) || (deltaSecs == 1) || (deltaSecs >= GarminHWM.toInt())) {
if ((isGarminSmartRecording.toInt() == 0) || (deltaSecs == 1) || (deltaSecs >= GarminHWM.toInt() && swim != Swim)) {

// no smart recording, or delta exceeds HW treshold, just insert the data
rideFile->appendPoint(secs, cadence, hr, distance, speed, torque, power,
Expand All @@ -211,10 +221,11 @@ TcxParser::endElement( const QString&, const QString&, const QString& qName)

} else {

// smart recording is on and delta is less than GarminHWM seconds.
for(int i = 1; i <= deltaSecs; i++) {
// smart recording is on and delta is less than GarminHWM seconds
// or it is pool swimming and we limit expansion for safety
for(int i = 1; i <= deltaSecs && i <= 10*GarminHWM.toInt(); i++) {
double weight = i/ deltaSecs;
double kph = prevPoint->kph + (deltaSpeed *weight);
double kph = (swim == Swim) ? speed : prevPoint->kph + (deltaSpeed *weight);
// need to make sure speed goes to zero
kph = kph > 0.35 ? kph : 0;
double cad = prevPoint->cad + (deltaCad * weight);
Expand Down Expand Up @@ -249,6 +260,34 @@ TcxParser::endElement( const QString&, const QString&, const QString& qName)
}
last_distance = distance;
last_time = time;
} else if (qName == "Lap") {
// for pool swimming, laps with distance 0 are pauses, without trackpoints
if (swim == Swim && distance == 0) {
// fill in the pause, partially if too long
for(int i = 1; i <= round(lapSecs) && i <= 10*GarminHWM.toInt(); i++)
rideFile->appendPoint(secs + i,
0.0,
0.0,
last_distance,
0.0,
0.0,
0.0,
0.0,
0.0, // lon
0.0, // lat
0.0, // headwind
0.0,
RideFile::NoTemp,
0.0,
0.0,0.0,0.0,0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0,0.0,
0.0, // vertical oscillation
0.0, // run cadence
0.0, // gct
lap);
last_time = last_time.addSecs(round(lapSecs));
}
}
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions src/TcxParser.h
Expand Up @@ -54,6 +54,8 @@ class TcxParser : public QXmlDefaultHandler

double last_distance;
double distance;
double lapSecs; // for pause intervals in pool swimming files
enum { NotSwim, MayBeSwim, Swim } swim; // to detect pool swimming files

bool first; // first ride found, when it may contain collections!
int lap;
Expand Down

0 comments on commit ea37db2

Please sign in to comment.