/* * 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 // for std::sort #include #include #include #include #define DATETIME_FORMAT "yyyy/MM/dd hh:mm:ss' UTC'" static int gcFileReaderRegistered = RideFileFactory::instance().registerReader( "gc", "GoldenCheetah Native Format", new GcFileReader()); RideFile * GcFileReader::openRideFile(QFile &file, QStringList &errors) 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); 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()); } } // read in metric overrides: // // // // 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 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 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, 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)); void GcFileReader::writeRideFile(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()); // write out in metric overrides: // // // // // write out the QMap tag/value pairs QDomElement overrides = doc.createElement("override"); root.appendChild(overrides); QMap >::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::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::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(); assert(present->secs); foreach (const RideFilePoint *point, ride->dataPoints()) { QDomElement sample = doc.createElement("sample"); samples.appendChild(sample); assert(present->secs); 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)) assert(false); if (file.write(xml) != xml.size()) assert(false); file.close(); }