mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-04-15 05:32:21 +00:00
Support TrainerRoad.com TCX Files
The TCX parser ignored samples where distance is zero even when speed and time are available. This broke reading files from TrainerRoad.com. Fixes #580.
This commit is contained in:
@@ -26,27 +26,21 @@
|
||||
// use stc strtod to bypass Qt toDouble() issues
|
||||
#include <stdlib.h>
|
||||
|
||||
TcxParser::TcxParser (RideFile* rideFile, QList<RideFile*> *rides)
|
||||
: rideFile(rideFile), rides(rides)
|
||||
TcxParser::TcxParser (RideFile* rideFile, QList<RideFile*> *rides) : rideFile(rideFile), rides(rides)
|
||||
{
|
||||
isGarminSmartRecording = appsettings->value(NULL, GC_GARMIN_SMARTRECORD,Qt::Checked);
|
||||
GarminHWM = appsettings->value(NULL, GC_GARMIN_HWMARK);
|
||||
if (GarminHWM.isNull() || GarminHWM.toInt() == 0)
|
||||
GarminHWM.setValue(25); // default to 25 seconds.
|
||||
|
||||
first = true;
|
||||
|
||||
isGarminSmartRecording = appsettings->value(NULL, GC_GARMIN_SMARTRECORD,Qt::Checked);
|
||||
GarminHWM = appsettings->value(NULL, GC_GARMIN_HWMARK);
|
||||
if (GarminHWM.isNull() || GarminHWM.toInt() == 0) GarminHWM.setValue(25); // default to 25 seconds.
|
||||
first = true;
|
||||
}
|
||||
|
||||
bool
|
||||
TcxParser::startElement( const QString&, const QString&,
|
||||
const QString& qName,
|
||||
const QXmlAttributes& qAttributes)
|
||||
TcxParser::startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& qAttributes)
|
||||
{
|
||||
buffer.clear();
|
||||
|
||||
if (qName == "Activity")
|
||||
{
|
||||
if (qName == "Activity") {
|
||||
|
||||
lap = 0;
|
||||
|
||||
if (first == true) first = false;
|
||||
@@ -59,163 +53,147 @@ TcxParser::startElement( const QString&, const QString&,
|
||||
|
||||
// if caller is looking for rides...
|
||||
if (rides) rides->append(rideFile);
|
||||
}
|
||||
else if (qName == "Lap")
|
||||
{
|
||||
// Use the time of the first lap as the time of the activity.
|
||||
if (lap == 0)
|
||||
{
|
||||
|
||||
} else if (qName == "Lap") {
|
||||
|
||||
// Use the time of the first lap as the time of the activity.
|
||||
if (lap == 0) {
|
||||
|
||||
start_time = convertToLocalTime(qAttributes.value("StartTime"));
|
||||
rideFile->setStartTime(start_time);
|
||||
|
||||
lastDistance = 0.0;
|
||||
last_distance = 0.0;
|
||||
last_time = start_time;
|
||||
}
|
||||
}
|
||||
lap++;
|
||||
}
|
||||
else if (qName == "Trackpoint")
|
||||
{
|
||||
|
||||
} else if (qName == "Trackpoint") {
|
||||
|
||||
power = 0.0;
|
||||
cadence = 0.0;
|
||||
speed = 0.0;
|
||||
headwind = 0.0;
|
||||
torque = 0;
|
||||
hr = 0.0;
|
||||
lat = 0.0;
|
||||
lon = 0.0;
|
||||
//alt = 0.0; // TCS from FIT files have not alt point for each trackpoint
|
||||
distance = -1; // nh - we set this to -1 so we can detect if there was a distance in the trackpoint.
|
||||
secs = 0;
|
||||
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool
|
||||
TcxParser::endElement( const QString&, const QString&, const QString& qName)
|
||||
{
|
||||
if (qName == "Time")
|
||||
{
|
||||
if (qName == "Time") {
|
||||
time = convertToLocalTime(buffer);
|
||||
}
|
||||
else if (qName == "DistanceMeters")
|
||||
{
|
||||
distance = buffer.toDouble() / 1000;
|
||||
}
|
||||
else if (qName == "Watts" || qName == "ns3:Watts")
|
||||
{
|
||||
power = buffer.toDouble();
|
||||
}
|
||||
else if (qName == "Value")
|
||||
{
|
||||
hr = buffer.toDouble();
|
||||
}
|
||||
else if (qName == "Cadence")
|
||||
{
|
||||
cadence = buffer.toDouble();
|
||||
}
|
||||
else if (qName == "AltitudeMeters")
|
||||
{
|
||||
alt = buffer.toDouble();
|
||||
}
|
||||
else if (qName == "LongitudeDegrees")
|
||||
{
|
||||
char *p;
|
||||
lon = strtod(buffer.toLatin1(), &p);
|
||||
}
|
||||
else if (qName == "LatitudeDegrees")
|
||||
{
|
||||
char *p;
|
||||
lat = strtod(buffer.toLatin1(), &p);
|
||||
}
|
||||
else if (qName == "Trackpoint")
|
||||
{
|
||||
// nh - there are track points that don't have any distance info. We need to ignore them
|
||||
if (distance>=0)
|
||||
{
|
||||
secs = start_time.secsTo(time);
|
||||
|
||||
} else if (qName == "DistanceMeters") { distance = buffer.toDouble() / 1000; }
|
||||
else if (qName == "Watts" || qName == "ns3:Watts") { power = buffer.toDouble(); }
|
||||
else if (qName == "Speed" || qName == "ns3:Speed") { speed = buffer.toDouble(); }
|
||||
else if (qName == "Value") { hr = buffer.toDouble(); }
|
||||
else if (qName == "Cadence") { cadence = buffer.toDouble(); }
|
||||
else if (qName == "AltitudeMeters") { alt = buffer.toDouble(); }
|
||||
else if (qName == "LongitudeDegrees") { char *p; lon = strtod(buffer.toLatin1(), &p); }
|
||||
else if (qName == "LatitudeDegrees") { char *p; lat = strtod(buffer.toLatin1(), &p); }
|
||||
else if (qName == "Trackpoint") {
|
||||
|
||||
// Some TCX files have Speed, some have Distance
|
||||
// Lets derive Speed from Distance or vice-versa
|
||||
// If we have neither Speed nor Distance then we
|
||||
// add a point with 0 for speed and distance
|
||||
if (speed == 0 || distance < 0) {
|
||||
|
||||
// compute the elapsed time and distance traveled since the
|
||||
// last recorded trackpoint
|
||||
double delta_t = last_time.secsTo(time);
|
||||
double delta_d = distance - lastDistance;
|
||||
if (delta_d<0)
|
||||
{
|
||||
delta_d=0;
|
||||
|
||||
// Derive speed from distance
|
||||
if (speed == 0 && distance >0) {
|
||||
|
||||
double delta_d = distance - last_distance;
|
||||
if (delta_d<0) delta_d=0;
|
||||
if (delta_t > 0.0) speed=delta_d / delta_t * 3600.0;
|
||||
|
||||
} else if (distance < 0) { // otherwise derive distance from speed
|
||||
|
||||
double delta_d = delta_t * speed / 3600.0;
|
||||
distance = last_distance + delta_d;
|
||||
|
||||
}
|
||||
|
||||
// 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 speed = 0.0;
|
||||
if (delta_t > 0.0)
|
||||
{
|
||||
speed=delta_d / delta_t * 3600.0;
|
||||
}
|
||||
|
||||
// Don't know what to do about torque
|
||||
double torque = 0.0;
|
||||
|
||||
// Time from beginning of activity
|
||||
double secs = start_time.secsTo(time);
|
||||
|
||||
// Record trackpoint
|
||||
|
||||
// for smart recording, the delta_t will not be constant
|
||||
// average all the calculations based on the previous
|
||||
// point.
|
||||
headwind = 0.0;
|
||||
if(rideFile->dataPoints().empty()) {
|
||||
// first point
|
||||
rideFile->appendPoint(secs, cadence, hr, distance,
|
||||
speed, torque, power, alt, lon, lat, headwind, 0.0, RideFile::noTemp, lap);
|
||||
}
|
||||
else {
|
||||
// assumption that the change in ride is linear... :)
|
||||
RideFilePoint *prevPoint = rideFile->dataPoints().back();
|
||||
double deltaSecs = secs - prevPoint->secs;
|
||||
double deltaCad = cadence - prevPoint->cad;
|
||||
double deltaHr = hr - prevPoint->hr;
|
||||
double deltaDist = distance - prevPoint->km;
|
||||
double deltaSpeed = speed - prevPoint->kph;
|
||||
double deltaTorque = torque - prevPoint->nm;
|
||||
double deltaPower = power - prevPoint->watts;
|
||||
double deltaAlt = alt - prevPoint->alt;
|
||||
double deltaLon = lon - prevPoint->lon;
|
||||
double deltaLat = lat - prevPoint->lat;
|
||||
|
||||
// Smart Recording High Water Mark.
|
||||
if ((isGarminSmartRecording.toInt() == 0) || (deltaSecs == 1) || (deltaSecs >= GarminHWM.toInt())) {
|
||||
// no smart recording, or delta exceeds HW treshold, just insert the data
|
||||
rideFile->appendPoint(secs, cadence, hr, distance,
|
||||
speed, torque, power, alt, lon, lat, headwind, 0.0, RideFile::noTemp, lap);
|
||||
}
|
||||
else {
|
||||
// smart recording is on and delta is less than GarminHWM seconds.
|
||||
for(int i = 1; i <= deltaSecs; i++) {
|
||||
double weight = i/ deltaSecs;
|
||||
double kph = prevPoint->kph + (deltaSpeed *weight);
|
||||
// need to make sure speed goes to zero
|
||||
kph = kph > 0.35 ? kph : 0;
|
||||
double cad = prevPoint->cad + (deltaCad * weight);
|
||||
cad = cad > 0.35 ? cad : 0;
|
||||
double lat = prevPoint->lat + (deltaLat * weight);
|
||||
double lon = prevPoint->lon + (deltaLon * weight);
|
||||
rideFile->appendPoint(
|
||||
prevPoint->secs + (deltaSecs * weight),
|
||||
prevPoint->cad + (deltaCad * weight),
|
||||
prevPoint->hr + (deltaHr * weight),
|
||||
prevPoint->km + (deltaDist * weight),
|
||||
kph,
|
||||
prevPoint->nm + (deltaTorque * weight),
|
||||
prevPoint->watts + (deltaPower * weight),
|
||||
prevPoint->alt + (deltaAlt * weight),
|
||||
lon, // lon
|
||||
lat, // lat
|
||||
headwind, // headwind
|
||||
0.0,
|
||||
RideFile::noTemp,
|
||||
lap);
|
||||
}
|
||||
prevPoint = rideFile->dataPoints().back();
|
||||
}
|
||||
}
|
||||
lastDistance = distance;
|
||||
}
|
||||
|
||||
// Record trackpoint
|
||||
|
||||
// for smart recording, the delta_t will not be constant
|
||||
// average all the calculations based on the previous
|
||||
// point.
|
||||
|
||||
if(rideFile->dataPoints().empty()) {
|
||||
|
||||
// first point
|
||||
rideFile->appendPoint(secs, cadence, hr, distance, speed, torque,
|
||||
power, alt, lon, lat, headwind, 0.0, RideFile::noTemp, lap);
|
||||
|
||||
} else {
|
||||
|
||||
// assumption that the change in ride is linear... :)
|
||||
RideFilePoint *prevPoint = rideFile->dataPoints().back();
|
||||
double deltaSecs = secs - prevPoint->secs;
|
||||
double deltaCad = cadence - prevPoint->cad;
|
||||
double deltaHr = hr - prevPoint->hr;
|
||||
double deltaDist = distance - prevPoint->km;
|
||||
double deltaSpeed = speed - prevPoint->kph;
|
||||
double deltaTorque = torque - prevPoint->nm;
|
||||
double deltaPower = power - prevPoint->watts;
|
||||
double deltaAlt = alt - prevPoint->alt;
|
||||
double deltaLon = lon - prevPoint->lon;
|
||||
double deltaLat = lat - prevPoint->lat;
|
||||
|
||||
// Smart Recording High Water Mark.
|
||||
if ((isGarminSmartRecording.toInt() == 0) || (deltaSecs == 1) || (deltaSecs >= GarminHWM.toInt())) {
|
||||
|
||||
// no smart recording, or delta exceeds HW treshold, just insert the data
|
||||
rideFile->appendPoint(secs, cadence, hr, distance, speed, torque, power,
|
||||
alt, lon, lat, headwind, 0.0, RideFile::noTemp, lap);
|
||||
|
||||
} else {
|
||||
|
||||
// smart recording is on and delta is less than GarminHWM seconds.
|
||||
for(int i = 1; i <= deltaSecs; i++) {
|
||||
double weight = i/ deltaSecs;
|
||||
double kph = prevPoint->kph + (deltaSpeed *weight);
|
||||
// need to make sure speed goes to zero
|
||||
kph = kph > 0.35 ? kph : 0;
|
||||
double cad = prevPoint->cad + (deltaCad * weight);
|
||||
cad = cad > 0.35 ? cad : 0;
|
||||
double lat = prevPoint->lat + (deltaLat * weight);
|
||||
double lon = prevPoint->lon + (deltaLon * weight);
|
||||
|
||||
rideFile->appendPoint(prevPoint->secs + (deltaSecs * weight),
|
||||
prevPoint->cad + (deltaCad * weight),
|
||||
prevPoint->hr + (deltaHr * weight),
|
||||
prevPoint->km + (deltaDist * weight),
|
||||
kph,
|
||||
prevPoint->nm + (deltaTorque * weight),
|
||||
prevPoint->watts + (deltaPower * weight),
|
||||
prevPoint->alt + (deltaAlt * weight),
|
||||
lon, // lon
|
||||
lat, // lat
|
||||
headwind, // headwind
|
||||
0.0,
|
||||
RideFile::noTemp,
|
||||
lap);
|
||||
}
|
||||
prevPoint = rideFile->dataPoints().back();
|
||||
}
|
||||
}
|
||||
last_distance = distance;
|
||||
last_time = time;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
|
||||
class TcxParser : public QXmlDefaultHandler
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
TcxParser(RideFile* rideFile, QList<RideFile*>*rides);
|
||||
|
||||
bool startElement( const QString&, const QString&, const QString&,
|
||||
const QXmlAttributes& );
|
||||
bool startElement( const QString&, const QString&, const QString&, const QXmlAttributes& );
|
||||
bool endElement( const QString&, const QString&, const QString& );
|
||||
|
||||
bool characters( const QString& );
|
||||
|
||||
RideFile* rideFile;
|
||||
@@ -44,26 +44,29 @@ public:
|
||||
private:
|
||||
|
||||
QString buffer;
|
||||
QVariant isGarminSmartRecording;
|
||||
QVariant GarminHWM;
|
||||
QVariant isGarminSmartRecording;
|
||||
QVariant GarminHWM;
|
||||
|
||||
QDateTime start_time;
|
||||
QDateTime last_time;
|
||||
QDateTime time;
|
||||
double lastDistance;
|
||||
double distance;
|
||||
QDateTime start_time;
|
||||
QDateTime last_time;
|
||||
QDateTime time;
|
||||
|
||||
bool first; // first ride found, when it may contain collections!
|
||||
int lap;
|
||||
double power;
|
||||
double cadence;
|
||||
double hr;
|
||||
double lastAltitude;
|
||||
double alt;
|
||||
double lat;
|
||||
double lon;
|
||||
double headwind;
|
||||
double last_distance;
|
||||
double distance;
|
||||
|
||||
bool first; // first ride found, when it may contain collections!
|
||||
int lap;
|
||||
double power;
|
||||
double cadence;
|
||||
double hr;
|
||||
double speed;
|
||||
double torque;
|
||||
double lastAltitude;
|
||||
double alt;
|
||||
double lat;
|
||||
double lon;
|
||||
double headwind;
|
||||
double secs;
|
||||
};
|
||||
|
||||
#endif // _TcxParser_h
|
||||
|
||||
|
||||
Reference in New Issue
Block a user