33
37
start = point->secs; \
36
#define tr(s) QObject::tr(s)
38
40
RideFile::RideFile(const QDateTime &startTime, double recIntSecs) :
39
41
startTime_(startTime), recIntSecs_(recIntSecs),
40
deviceType_("unknown"), data(NULL)
42
deviceType_("unknown"), data(NULL), weight_(0)
42
44
command = new RideFileCommand(this);
45
RideFile::RideFile() : recIntSecs_(0.0), deviceType_("unknown"), data(NULL)
47
RideFile::RideFile() : recIntSecs_(0.0), deviceType_("unknown"), data(NULL), weight_(0)
47
49
command = new RideFileCommand(this);
50
52
RideFile::~RideFile()
52
55
foreach(RideFilePoint *point, dataPoints_)
58
//!!! if (data) delete data; // need a mechanism to notify the editor
65
69
case RideFile::kph: return QString(tr("Speed"));
66
70
case RideFile::nm: return QString(tr("Torque"));
67
71
case RideFile::watts: return QString(tr("Power"));
72
case RideFile::xPower: return QString(tr("xPower"));
73
case RideFile::NP: return QString(tr("Normalized Power"));
68
74
case RideFile::alt: return QString(tr("Altitude"));
69
75
case RideFile::lon: return QString(tr("Longitude"));
70
76
case RideFile::lat: return QString(tr("Latitude"));
71
77
case RideFile::headwind: return QString(tr("Headwind"));
72
case RideFile::interval: return QString(tr("Interval"));
73
default: return QString(tr("Unknown"));
78
case RideFile::slope: return QString(tr("Slope"));
79
case RideFile::temp: return QString(tr("Temperature"));
80
case RideFile::lrbalance: return QString(tr("Left/Right Balance"));
81
case RideFile::interval: return QString(tr("Interval"));
82
case RideFile::vam: return QString(tr("VAM"));
83
case RideFile::wattsKg: return QString(tr("Watts per Kilogram"));
84
default: return QString(tr("Unknown"));
89
RideFile::unitName(SeriesType series, MainWindow *main)
91
bool useMetricUnits = main->useMetricUnits;
94
case RideFile::secs: return QString(tr("seconds"));
95
case RideFile::cad: return QString(tr("rpm"));
96
case RideFile::hr: return QString(tr("bpm"));
97
case RideFile::km: return QString(useMetricUnits ? tr("km") : tr("miles"));
98
case RideFile::kph: return QString(useMetricUnits ? tr("kph") : tr("mph"));
99
case RideFile::nm: return QString(tr("N"));
100
case RideFile::watts: return QString(tr("watts"));
101
case RideFile::xPower: return QString(tr("watts"));
102
case RideFile::NP: return QString(tr("watts"));
103
case RideFile::alt: return QString(useMetricUnits ? tr("metres") : tr("feet"));
104
case RideFile::lon: return QString(tr("lon"));
105
case RideFile::lat: return QString(tr("lat"));
106
case RideFile::headwind: return QString(tr("kph"));
107
case RideFile::slope: return QString(tr("%"));
108
case RideFile::temp: return QString(tr("°C"));
109
case RideFile::lrbalance: return QString(tr("%"));
110
case RideFile::interval: return QString(tr("Interval"));
111
case RideFile::vam: return QString(tr("meters per hour"));
112
case RideFile::wattsKg: return QString(useMetricUnits ? tr("watts/kg") : tr("watts/lb"));
113
default: return QString(tr("Unknown"));
79
118
RideFile::clearIntervals()
151
190
QVector<RideFilePoint*>::const_iterator i = std::lower_bound(
152
191
dataPoints_.begin(), dataPoints_.end(), &p, ComparePointSecs());
153
192
if (i == dataPoints_.end())
154
return dataPoints_.size();
193
return dataPoints_.size()-1;
155
194
return i - dataPoints_.begin();
169
208
return i - dataPoints_.begin();
172
void RideFile::writeAsCsv(QFile &file, bool bIsMetric) const
175
// Use the column headers that make WKO+ happy.
177
QTextStream out(&file);
180
out << "Minutes,Torq (N-m),MPH,Watts,Miles,Cadence,Hrate,ID,Altitude (feet)\n";
181
convertUnit = MILES_PER_KM;
184
out << "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)\n";
188
foreach (const RideFilePoint *point, dataPoints()) {
189
if (point->secs == 0.0)
191
out << point->secs/60.0;
193
out << ((point->nm >= 0) ? point->nm : 0.0);
195
out << ((point->kph >= 0) ? (point->kph * convertUnit) : 0.0);
197
out << ((point->watts >= 0) ? point->watts : 0.0);
199
out << point->km * convertUnit;
205
out << point->interval;
214
212
RideFileFactory *RideFileFactory::instance_;
243
252
return QRegExp(s.arg(suffixList.join("|")), Qt::CaseInsensitive);
246
RideFile *RideFileFactory::openRideFile(QFile &file,
247
QStringList &errors) const
256
RideFileFactory::writeRideFile(MainWindow *main, const RideFile *ride, QFile &file, QString format) const
258
// get the ride file writer for this format
259
RideFileReader *reader = readFuncs_.value(format.toLower());
262
if (!reader) return false;
263
else return reader->writeRideFile(main, ride, file);
266
RideFile *RideFileFactory::openRideFile(MainWindow *main, QFile &file,
267
QStringList &errors, QList<RideFile*> *rideList) const
249
269
QString suffix = file.fileName();
250
270
int dot = suffix.lastIndexOf(".");
252
272
suffix.remove(0, dot + 1);
253
273
RideFileReader *reader = readFuncs_.value(suffix.toLower());
255
RideFile *result = reader->openRideFile(file, errors);
275
//qDebug()<<"open"<<file.fileName()<<"start:"<<QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
276
RideFile *result = reader->openRideFile(file, errors, rideList);
277
//qDebug()<<"open"<<file.fileName()<<"end:"<<QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
257
279
// NULL returned to indicate openRide failed
281
result->mainwindow = main;
259
282
if (result->intervals().empty()) result->fillInIntervals();
261
285
// override the file ride time with that set from the filename
262
286
// but only if it matches the GC format
263
287
QFileInfo fileInfo(file.fileName());
271
295
result->setStartTime(datetime);
274
result->setTag("Filename", file.fileName());
298
// legacy support for .notes file
299
QString notesFileName = fileInfo.absolutePath() + '/' + fileInfo.baseName() + ".notes";
300
QFile notesFile(notesFileName);
302
// read it in if it exists and "Notes" is not already set
303
if (result->getTag("Notes", "") == "" && notesFile.exists() &&
304
notesFile.open(QFile::ReadOnly | QFile::Text)) {
305
QTextStream in(¬esFile);
306
result->setTag("Notes", in.readAll());
310
// Construct the summary text used on the calendar
311
QString calendarText;
312
foreach (FieldDefinition field, main->rideMetadata()->getFields()) {
313
if (field.diary == true && result->getTag(field.name, "") != "") {
314
calendarText += QString("%1\n")
315
.arg(result->getTag(field.name, ""));
318
result->setTag("Calendar Text", calendarText);
320
// set other "special" fields
321
result->setTag("Filename", QFileInfo(file.fileName()).fileName());
322
result->setTag("Device", result->deviceType());
323
result->setTag("File Format", result->fileFormat());
275
324
result->setTag("Athlete", QFileInfo(file).dir().dirName());
325
result->setTag("Year", result->startTime().toString("yyyy"));
326
result->setTag("Month", result->startTime().toString("MMMM"));
327
result->setTag("Weekday", result->startTime().toString("ddd"));
276
329
DataProcessorFactory::instance().autoProcess(result);
331
// what data is present - after processor in case 'derived' or adjusted
334
if (result->areDataPresent()->secs) flags += 'T'; // time
336
if (result->areDataPresent()->km) flags += 'D'; // distance
338
if (result->areDataPresent()->kph) flags += 'S'; // speed
340
if (result->areDataPresent()->watts) flags += 'P'; // Power
342
if (result->areDataPresent()->hr) flags += 'H'; // Heartrate
344
if (result->areDataPresent()->cad) flags += 'C'; // cadence
346
if (result->areDataPresent()->nm) flags += 'N'; // Torque
348
if (result->areDataPresent()->alt) flags += 'A'; // Altitude
350
if (result->areDataPresent()->lat ||
351
result->areDataPresent()->lon ) flags += 'G'; // GPS
353
if (result->areDataPresent()->headwind) flags += 'W'; // Windspeed
355
if (result->areDataPresent()->temp) flags += 'E'; // Temperature
357
if (result->areDataPresent()->lrbalance) flags += 'B'; // Left/Right Balance, TODO Walibu, unsure about this flag? 'B' ok?
359
result->setTag("Data", flags);
288
372
filters << ("*." + i.key());
290
374
// This will read the user preferences and change the file list order as necessary:
291
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
292
QVariant isAscending = settings->value(GC_ALLRIDES_ASCENDING,Qt::Checked);
293
375
QFlags<QDir::Filter> spec = QDir::Files;
294
376
#ifdef Q_OS_WIN32
295
377
spec |= QDir::Hidden;
297
if(isAscending.toInt()>0){
298
return dir.entryList(filters, spec, QDir::Name);
300
return dir.entryList(filters, spec, QDir::Name|QDir::Reversed);
379
return dir.entryList(filters, spec, QDir::Name);
303
382
void RideFile::appendPoint(double secs, double cad, double hr, double km,
304
383
double kph, double nm, double watts, double alt,
305
double lon, double lat, double headwind, int interval)
384
double lon, double lat, double headwind,
385
double slope, double temp, double lrbalance, int interval)
307
387
// negative values are not good, make them zero
308
// although alt, lat, lon, headwind can be negative of course!
388
// although alt, lat, lon, headwind, slope and temperature can be negative of course!
309
389
if (!isfinite(secs) || secs<0) secs=0;
310
390
if (!isfinite(cad) || cad<0) cad=0;
311
391
if (!isfinite(hr) || hr<0) hr=0;
316
396
if (!isfinite(interval) || interval<0) interval=0;
318
398
dataPoints_.append(new RideFilePoint(secs, cad, hr, km, kph,
319
nm, watts, alt, lon, lat, headwind, interval));
320
dataPresent.secs |= (secs != 0);
321
dataPresent.cad |= (cad != 0);
322
dataPresent.hr |= (hr != 0);
323
dataPresent.km |= (km != 0);
324
dataPresent.kph |= (kph != 0);
325
dataPresent.nm |= (nm != 0);
326
dataPresent.watts |= (watts != 0);
327
dataPresent.alt |= (alt != 0);
328
dataPresent.lon |= (lon != 0);
329
dataPresent.lat |= (lat != 0);
399
nm, watts, alt, lon, lat, headwind, slope, temp, lrbalance, interval));
400
dataPresent.secs |= (secs != 0);
401
dataPresent.cad |= (cad != 0);
402
dataPresent.hr |= (hr != 0);
403
dataPresent.km |= (km != 0);
404
dataPresent.kph |= (kph != 0);
405
dataPresent.nm |= (nm != 0);
406
dataPresent.watts |= (watts != 0);
407
dataPresent.alt |= (alt != 0);
408
dataPresent.lon |= (lon != 0);
409
dataPresent.lat |= (lat != 0);
330
410
dataPresent.headwind |= (headwind != 0);
411
dataPresent.slope |= (slope != 0);
412
dataPresent.temp |= (temp != noTemp);
413
dataPresent.lrbalance|= (lrbalance != 0);
331
414
dataPresent.interval |= (interval != 0);
417
void RideFile::appendPoint(const RideFilePoint &point)
419
dataPoints_.append(new RideFilePoint(point.secs,point.cad,point.hr,point.km,point.kph,point.nm,point.watts,point.alt,point.lon,point.lat,
420
point.headwind, point.slope, point.temp, point.lrbalance, point.interval));
335
424
RideFile::setDataPresent(SeriesType series, bool value)
346
435
case lon : dataPresent.lon = value; break;
347
436
case lat : dataPresent.lat = value; break;
348
437
case headwind : dataPresent.headwind = value; break;
438
case slope : dataPresent.slope = value; break;
439
case temp : dataPresent.temp = value; break;
440
case lrbalance : dataPresent.lrbalance = value; break;
349
441
case interval : dataPresent.interval = value; break;
350
443
case none : break;
366
459
case lon : return dataPresent.lon; break;
367
460
case lat : return dataPresent.lat; break;
368
461
case headwind : return dataPresent.headwind; break;
462
case slope : return dataPresent.slope; break;
463
case temp : return dataPresent.temp; break;
464
case lrbalance : return dataPresent.lrbalance; break;
369
465
case interval : return dataPresent.interval; break;
467
case none : return false; break;
386
483
case lon : dataPoints_[index]->lon = value; break;
387
484
case lat : dataPoints_[index]->lat = value; break;
388
485
case headwind : dataPoints_[index]->headwind = value; break;
486
case slope : dataPoints_[index]->slope = value; break;
487
case temp : dataPoints_[index]->temp = value; break;
488
case lrbalance : dataPoints_[index]->lrbalance = value; break;
389
489
case interval : dataPoints_[index]->interval = value; break;
390
491
case none : break;
395
RideFile::getPointValue(int index, SeriesType series)
496
RideFilePoint::value(RideFile::SeriesType series) const
397
498
switch (series) {
398
case secs : return dataPoints_[index]->secs; break;
399
case cad : return dataPoints_[index]->cad; break;
400
case hr : return dataPoints_[index]->hr; break;
401
case km : return dataPoints_[index]->km; break;
402
case kph : return dataPoints_[index]->kph; break;
403
case nm : return dataPoints_[index]->nm; break;
404
case watts : return dataPoints_[index]->watts; break;
405
case alt : return dataPoints_[index]->alt; break;
406
case lon : return dataPoints_[index]->lon; break;
407
case lat : return dataPoints_[index]->lat; break;
408
case headwind : return dataPoints_[index]->headwind; break;
409
case interval : return dataPoints_[index]->interval; break;
499
case RideFile::secs : return secs; break;
500
case RideFile::cad : return cad; break;
501
case RideFile::hr : return hr; break;
502
case RideFile::km : return km; break;
503
case RideFile::kph : return kph; break;
504
case RideFile::nm : return nm; break;
505
case RideFile::watts : return watts; break;
506
case RideFile::alt : return alt; break;
507
case RideFile::lon : return lon; break;
508
case RideFile::lat : return lat; break;
509
case RideFile::headwind : return headwind; break;
510
case RideFile::slope : return slope; break;
511
case RideFile::temp : return temp; break;
512
case RideFile::lrbalance : return lrbalance; break;
513
case RideFile::interval : return interval; break;
516
case RideFile::none : break;
412
return 0.0; // shutup the compiler
522
RideFile::getPointValue(int index, SeriesType series) const
524
return dataPoints_[index]->value(series);
528
RideFile::getPoint(int index, SeriesType series) const
530
double value = getPointValue(index, series);
531
if (series==RideFile::temp && value == RideFile::noTemp)
533
else if (series==RideFile::wattsKg)
423
546
case kph : return 4; break;
424
547
case nm : return 2; break;
425
548
case watts : return 0; break;
549
case xPower : return 0; break;
550
case NP : return 0; break;
426
551
case alt : return 3; break;
427
552
case lon : return 6; break;
428
553
case lat : return 6; break;
429
554
case headwind : return 4; break;
555
case slope : return 1; break;
556
case temp : return 1; break;
430
557
case interval : return 0; break;
558
case vam : return 0; break;
559
case wattsKg : return 2; break;
560
case lrbalance : return 1; break;
431
561
case none : break;
433
563
return 2; // default
439
569
switch (series) {
440
570
case secs : return 999999; break;
441
case cad : return 300; break;
442
case hr : return 300; break;
571
case cad : return 255; break;
572
case hr : return 255; break;
443
573
case km : return 999999; break;
444
case kph : return 999; break;
445
case nm : return 999; break;
446
case watts : return 4000; break;
574
case kph : return 150; break;
575
case nm : return 100; break;
576
case watts : return 2500; break;
577
case NP : return 2500; break;
578
case xPower : return 2500; break;
447
579
case alt : return 8850; break; // mt everest is highest point above sea level
448
580
case lon : return 180; break;
449
581
case lat : return 90; break;
450
582
case headwind : return 999; break;
583
case slope : return 100; break;
584
case temp : return 100; break;
451
585
case interval : return 999; break;
586
case vam : return 9999; break;
587
case wattsKg : return 50; break;
588
case lrbalance : return 100; break;
452
589
case none : break;
454
591
return 9999; // default
465
602
case kph : return 0; break;
466
603
case nm : return 0; break;
467
604
case watts : return 0; break;
605
case xPower : return 0; break;
606
case NP : return 0; break;
468
607
case alt : return -413; break; // the Red Sea is lowest land point on earth
469
608
case lon : return -180; break;
470
609
case lat : return -90; break;
471
610
case headwind : return -999; break;
611
case slope : return -100; break;
612
case temp : return -100; break;
472
613
case interval : return 0; break;
614
case vam : return 0; break;
615
case wattsKg : return 0; break;
616
case lrbalance : return 0; break;
473
617
case none : break;
475
619
return 0; // default
505
649
RideFile::emitSaved()
511
656
RideFile::emitReverted()
517
663
RideFile::emitModified()
670
RideFile::getWeight()
672
if (weight_) return weight_; // cached value
675
if ((weight_ = getTag("Weight", "0.0").toDouble()) > 0) {
680
QList<SummaryMetrics> measures = mainwindow->metricDB->getAllMeasuresFor(QDateTime::fromString("Jan 1 00:00:00 1900"), startTime());
681
int i = measures.count()-1;
684
if ((weight_ = measures[i].getText("Weight", "0.0").toDouble()) > 0) {
693
weight_ = appsettings->cvalue(mainwindow->cyclist, GC_WEIGHT, "75.0").toString().toDouble(); // default to 75kg
695
// if set to zero in global options then override it.
696
// it must not be zero!!!
697
if (weight_ <= 0.00) weight_ = 75.00;