mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 00:28:42 +00:00
There still some assert left in the code, but removed a fair number of the examples where, its just as easy to handle the condition gracefully, without crashing. By 3.1 we will have eradicated assert from the code.
278 lines
10 KiB
C++
278 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net),
|
|
*
|
|
* 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 "GcRideFile.h"
|
|
#include <algorithm> // for std::sort
|
|
#include <QDomDocument>
|
|
#include <QVector>
|
|
|
|
#include <QDebug>
|
|
|
|
#define DATETIME_FORMAT "yyyy/MM/dd hh:mm:ss' UTC'"
|
|
|
|
static int gcFileReaderRegistered =
|
|
RideFileFactory::instance().registerReader(
|
|
"gc", "GoldenCheetah XML", new GcFileReader());
|
|
|
|
RideFile *
|
|
GcFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
|
|
{
|
|
QDomDocument doc("GoldenCheetah");
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
errors << "Could not open file.";
|
|
return NULL;
|
|
}
|
|
|
|
bool parsed = doc.setContent(&file);
|
|
file.close();
|
|
if (!parsed) {
|
|
errors << "Could not parse file.";
|
|
return NULL;
|
|
}
|
|
|
|
RideFile *rideFile = new RideFile();
|
|
QDomElement root = doc.documentElement();
|
|
QDomNode attributes = root.firstChildElement("attributes");
|
|
|
|
for (QDomElement attr = attributes.firstChildElement("attribute");
|
|
!attr.isNull(); attr = attr.nextSiblingElement("attribute")) {
|
|
QString key = attr.attribute("key");
|
|
QString value = attr.attribute("value");
|
|
if (key == "Device type")
|
|
rideFile->setDeviceType(value);
|
|
else if (key == "File Format")
|
|
rideFile->setFileFormat(value);
|
|
if (key == "Start time") {
|
|
// by default QDateTime is localtime - the source however is UTC
|
|
QDateTime aslocal = QDateTime::fromString(value, DATETIME_FORMAT);
|
|
// construct in UTC so we can honour the conversion to localtime
|
|
QDateTime asUTC = QDateTime(aslocal.date(), aslocal.time(), Qt::UTC);
|
|
// now set in localtime
|
|
rideFile->setStartTime(asUTC.toLocalTime());
|
|
}
|
|
if (key == "Identifier") {
|
|
rideFile->setId(value);
|
|
}
|
|
}
|
|
|
|
// read in metric overrides:
|
|
// <override>
|
|
// <metric name="skiba_bike_score" value="100"/>
|
|
// <metric name="average_speed" secs="3600" km="30"/>
|
|
// </override>
|
|
|
|
QDomNode overrides = root.firstChildElement("override");
|
|
if (!overrides.isNull()) {
|
|
|
|
for (QDomElement override = overrides.firstChildElement("metric");
|
|
!override.isNull();
|
|
override = override.nextSiblingElement("metric")) {
|
|
|
|
// setup the metric overrides QMap
|
|
QMap<QString, QString> bsm;
|
|
|
|
// for now only value is known to be maintained
|
|
bsm.insert("value", override.attribute("value"));
|
|
|
|
// insert into the rideFile overrides
|
|
rideFile->metricOverrides.insert(override.attribute("name"), bsm);
|
|
}
|
|
}
|
|
|
|
// read in the name/value metadata pairs
|
|
QDomNode tags = root.firstChildElement("tags");
|
|
if (!tags.isNull()) {
|
|
|
|
for (QDomElement tag = tags.firstChildElement("tag");
|
|
!tag.isNull();
|
|
tag = tag.nextSiblingElement("tag")) {
|
|
|
|
rideFile->setTag(tag.attribute("name"), tag.attribute("value"));
|
|
}
|
|
}
|
|
|
|
QVector<double> intervalStops; // used to set the interval number for each point
|
|
RideFileInterval add; // used to add each named interval to RideFile
|
|
QDomNode intervals = root.firstChildElement("intervals");
|
|
if (!intervals.isNull()) {
|
|
for (QDomElement interval = intervals.firstChildElement("interval");
|
|
!interval.isNull(); interval = interval.nextSiblingElement("interval")) {
|
|
|
|
// record the stops for old-style datapoint interval numbering
|
|
double stop = interval.attribute("stop").toDouble();
|
|
intervalStops.append(stop);
|
|
|
|
// add a new interval to the new-style interval ranges
|
|
add.stop = stop;
|
|
add.start = interval.attribute("start").toDouble();
|
|
add.name = interval.attribute("name");
|
|
rideFile->addInterval(add.start, add.stop, add.name);
|
|
}
|
|
}
|
|
std::sort(intervalStops.begin(), intervalStops.end()); // just in case
|
|
int interval = 0;
|
|
|
|
QDomElement samples = root.firstChildElement("samples");
|
|
if (samples.isNull()) return rideFile; // manual file will have no samples
|
|
|
|
bool recIntSet = false;
|
|
for (QDomElement sample = samples.firstChildElement("sample");
|
|
!sample.isNull(); sample = sample.nextSiblingElement("sample")) {
|
|
double secs, cad, hr, km, kph, nm, watts, alt, lon, lat;
|
|
double headwind = 0.0;
|
|
secs = sample.attribute("secs", "0.0").toDouble();
|
|
cad = sample.attribute("cad", "0.0").toDouble();
|
|
hr = sample.attribute("hr", "0.0").toDouble();
|
|
km = sample.attribute("km", "0.0").toDouble();
|
|
kph = sample.attribute("kph", "0.0").toDouble();
|
|
nm = sample.attribute("nm", "0.0").toDouble();
|
|
watts = sample.attribute("watts", "0.0").toDouble();
|
|
alt = sample.attribute("alt", "0.0").toDouble();
|
|
lon = sample.attribute("lon", "0.0").toDouble();
|
|
lat = sample.attribute("lat", "0.0").toDouble();
|
|
while ((interval < intervalStops.size()) && (secs >= intervalStops[interval]))
|
|
++interval;
|
|
rideFile->appendPoint(secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, 0.0, RideFile::noTemp, 0, interval);
|
|
if (!recIntSet) {
|
|
rideFile->setRecIntSecs(sample.attribute("len").toDouble());
|
|
recIntSet = true;
|
|
}
|
|
}
|
|
|
|
if (!recIntSet) {
|
|
errors << "no samples in ride file";
|
|
return NULL;
|
|
}
|
|
|
|
return rideFile;
|
|
}
|
|
|
|
// normal precision (Qt defaults)
|
|
#define add_sample(name) \
|
|
if (present->name) \
|
|
sample.setAttribute(#name, QString("%1").arg(point->name));
|
|
|
|
// high precision (6 decimals)
|
|
#define add_sample_hp(name) \
|
|
if (present->name) \
|
|
sample.setAttribute(#name, QString("%1").arg(point->name, 0, 'g', 11));
|
|
bool
|
|
GcFileReader::writeRideFile(Context *,const RideFile *ride, QFile &file) const
|
|
{
|
|
QDomDocument doc("GoldenCheetah");
|
|
QDomElement root = doc.createElement("ride");
|
|
doc.appendChild(root);
|
|
|
|
QDomElement attributes = doc.createElement("attributes");
|
|
root.appendChild(attributes);
|
|
|
|
QDomElement attribute = doc.createElement("attribute");
|
|
attributes.appendChild(attribute);
|
|
attribute.setAttribute("key", "Start time");
|
|
attribute.setAttribute(
|
|
"value", ride->startTime().toUTC().toString(DATETIME_FORMAT));
|
|
attribute = doc.createElement("attribute");
|
|
attributes.appendChild(attribute);
|
|
attribute.setAttribute("key", "Device type");
|
|
attribute.setAttribute("value", ride->deviceType());
|
|
attribute = doc.createElement("attribute");
|
|
attributes.appendChild(attribute);
|
|
attribute.setAttribute("key", "Identifier");
|
|
attribute.setAttribute("value", ride->id());
|
|
|
|
// write out in metric overrides:
|
|
// <override>
|
|
// <metric name="skiba_bike_score" value="100"/>
|
|
// <metric name="average_speed" secs="3600" km="30"/>
|
|
// </override>
|
|
// write out the QMap tag/value pairs
|
|
QDomElement overrides = doc.createElement("override");
|
|
root.appendChild(overrides);
|
|
QMap<QString,QMap<QString, QString> >::const_iterator k;
|
|
for (k=ride->metricOverrides.constBegin(); k != ride->metricOverrides.constEnd(); k++) {
|
|
|
|
// may not contain anything
|
|
if (k.value().isEmpty()) continue;
|
|
|
|
QDomElement override = doc.createElement("metric");
|
|
overrides.appendChild(override);
|
|
|
|
// metric name
|
|
override.setAttribute("name", k.key());
|
|
|
|
// key/value pairs
|
|
QMap<QString, QString>::const_iterator j;
|
|
for (j=k.value().constBegin(); j != k.value().constEnd(); j++) {
|
|
override.setAttribute(j.key(), j.value());
|
|
}
|
|
}
|
|
|
|
// write out the QMap tag/value pairs
|
|
QDomElement tags = doc.createElement("tags");
|
|
root.appendChild(tags);
|
|
QMap<QString,QString>::const_iterator i;
|
|
for (i=ride->tags().constBegin(); i != ride->tags().constEnd(); i++) {
|
|
QDomElement tag = doc.createElement("tag");
|
|
tags.appendChild(tag);
|
|
|
|
tag.setAttribute("name", i.key());
|
|
tag.setAttribute("value", i.value());
|
|
}
|
|
|
|
if (!ride->intervals().empty()) {
|
|
QDomElement intervals = doc.createElement("intervals");
|
|
root.appendChild(intervals);
|
|
foreach (RideFileInterval i, ride->intervals()) {
|
|
QDomElement interval = doc.createElement("interval");
|
|
intervals.appendChild(interval);
|
|
interval.setAttribute("name", i.name);
|
|
interval.setAttribute("start", QString("%1").arg(i.start));
|
|
interval.setAttribute("stop", QString("%1").arg(i.stop));
|
|
}
|
|
}
|
|
|
|
if (!ride->dataPoints().empty()) {
|
|
QDomElement samples = doc.createElement("samples");
|
|
root.appendChild(samples);
|
|
const RideFileDataPresent *present = ride->areDataPresent();
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
QDomElement sample = doc.createElement("sample");
|
|
samples.appendChild(sample);
|
|
add_sample_hp(secs);
|
|
add_sample(cad);
|
|
add_sample(hr);
|
|
add_sample(km);
|
|
add_sample(kph);
|
|
add_sample(nm);
|
|
add_sample(watts);
|
|
add_sample(alt);
|
|
add_sample_hp(lon);
|
|
add_sample_hp(lat);
|
|
sample.setAttribute("len", QString("%1").arg(ride->recIntSecs()));
|
|
}
|
|
}
|
|
|
|
QByteArray xml = doc.toByteArray(4);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
return false;
|
|
if (file.write(xml) != xml.size())
|
|
return false;
|
|
file.close();
|
|
return true;
|
|
}
|