From d6690cd5aef194b7f1fe59cfc8a9fbf645c08ace Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sun, 14 Jun 2015 09:32:01 +0100 Subject: [PATCH] Add W'bal Time In Zone (1 of 3) .. adding it to the CPX file --- src/RideFile.cpp | 4 + src/RideFile.h | 1 + src/RideFileCache.cpp | 196 ++++++++++++++++++++++++++++-------------- src/RideFileCache.h | 12 ++- 4 files changed, 145 insertions(+), 68 deletions(-) diff --git a/src/RideFile.cpp b/src/RideFile.cpp index 72f1aa671..60c70b54c 100644 --- a/src/RideFile.cpp +++ b/src/RideFile.cpp @@ -1086,6 +1086,7 @@ RideFile::setDataPresent(SeriesType series, bool value) case gear : dataPresent.gear = value; break; case interval : dataPresent.interval = value; break; case wprime : dataPresent.wprime = value; break; + case wbal : break; // not present default: case none : break; } @@ -1389,6 +1390,7 @@ RideFile::decimalsFor(SeriesType series) case rcontact : return 1; break; case gear : return 2; break; case wprime : return 0; break; + case wbal : return 0; break; default: case none : break; } @@ -1444,6 +1446,7 @@ RideFile::maximumFor(SeriesType series) case rcontact : return 1000; break; case gear : return 7; break; // 53x8 case wprime : return 99999; break; + case wbal : return 100; break; // wbal is from 0% used to 100% used default : case none : break; } @@ -1499,6 +1502,7 @@ RideFile::minimumFor(SeriesType series) case rcontact : return 0; break; case gear : return 0; break; case wprime : return 0; break; + case wbal: return 0; break; default : case none : break; } diff --git a/src/RideFile.h b/src/RideFile.h index 65b940295..019fd4d23 100644 --- a/src/RideFile.h +++ b/src/RideFile.h @@ -191,6 +191,7 @@ class RideFile : public QObject // QObject to emit signals aPower, wprime, aTISS, anTISS, smo2, thb, rvert, rcad, rcontact, gear, o2hb, hhb, lpco, rpco, lppb, rppb, lppe, rppe, lpppb, rpppb, lpppe, rpppe, + wbal, none }; // none must ALWAYS be last enum specialValues { NoTemp = -255 }; diff --git a/src/RideFileCache.cpp b/src/RideFileCache.cpp index 35b3655fb..6307500a7 100644 --- a/src/RideFileCache.cpp +++ b/src/RideFileCache.cpp @@ -24,6 +24,7 @@ #include "Zones.h" #include "HrZones.h" #include "PaceZones.h" +#include "WPrime.h" // for wbal zones #include "LTMSettings.h" // getAllBestsFor needs this #include // for pow() @@ -66,6 +67,7 @@ RideFileCache::RideFileCache(Context *context, QString fileName, double weight, wattsKgDistribution.resize(0); aPowerDistribution.resize(0); smo2Distribution.resize(0); + wbalDistribution.resize(0); // time in zone are fixed to 10 zone max wattsTimeInZone.resize(10); @@ -74,6 +76,7 @@ RideFileCache::RideFileCache(Context *context, QString fileName, double weight, hrCPTimeInZone.resize(4); // zero, I, II, III paceTimeInZone.resize(10); paceCPTimeInZone.resize(4); // zero, I, II, III + wbalTimeInZone.resize(4); // 0-25, 25-50, 50-75, 75% + // Get info for ride file and cache file QFileInfo rideFileInfo(rideFileName); @@ -247,12 +250,14 @@ static long offsetForTiz(RideFileCacheHeader head, RideFile::SeriesType series) offset += head.wattsKgDistCount * sizeof(float); offset += head.aPowerDistCount * sizeof(float); offset += head.smo2DistCount * sizeof(float); + offset += head.wbalDistCount * sizeof(float); - // tiz ist currently just for RideFile:watts, RideFile:hr and RideFile:kph series. - // watts is first - so move on with offset only for 'hr' and 'kph' + // tiz ist currently just for RideFile:watts, RideFile:hr, RideFile:kph and RideFile:wbal + // watts is first - so move on with offset only for 'hr' and 'kph' and 'wbal' // structure for "tiz" data - watts(10)/CPwatts(4)/HR(10)/CPhr(4)/PACE(10)/CPpace if (series == RideFile::hr) offset += (10+4) * sizeof(float); if (series == RideFile::kph) offset += 2*(10+4) * sizeof(float); + if (series == RideFile::wbal) offset += 3*(10+4) * sizeof(float); return offset; } @@ -418,6 +423,7 @@ RideFileCache::RideFileCache(RideFile *ride) : wattsKgDistribution.resize(0); aPowerDistribution.resize(0); smo2Distribution.resize(0); + wbalDistribution.resize(0); // time in zone are fixed to 10 zone max wattsTimeInZone.resize(10); @@ -426,6 +432,7 @@ RideFileCache::RideFileCache(RideFile *ride) : hrCPTimeInZone.resize(4); paceTimeInZone.resize(10); paceCPTimeInZone.resize(4); + wbalTimeInZone.resize(4); WEIGHT = ride->getWeight(); ride->recalculateDerivedSeries(); // accel and others @@ -464,6 +471,7 @@ RideFileCache::decimalsFor(RideFile::SeriesType series) case RideFile::wattsKg : return 2; break; case RideFile::aPower : return 0; break; case RideFile::smo2 : return 0; break; + case RideFile::wbal : return 0; break; case RideFile::lrbalance : return 1; break; case RideFile::wprime : return 0; break; case RideFile::none : break; @@ -656,6 +664,10 @@ RideFileCache::distributionArray(RideFile::SeriesType series) return smo2DistributionDouble; break; + case RideFile::wbal: + return wbalDistributionDouble; + break; + case RideFile::wattsKg: return wattsKgDistributionDouble; break; @@ -781,6 +793,7 @@ void RideFileCache::RideFileCache::compute() computeDistribution(wattsKgDistribution, RideFile::wattsKg); computeDistribution(aPowerDistribution, RideFile::aPower); computeDistribution(smo2Distribution, RideFile::smo2); + computeDistribution(wbalDistribution, RideFile::wbal); // wait for them threads thread1.wait(); @@ -827,6 +840,7 @@ void RideFileCache::RideFileCache::compute() doubleArrayForDistribution(wattsKgDistributionDouble, wattsKgDistribution); doubleArrayForDistribution(aPowerDistributionDouble, aPowerDistribution); doubleArrayForDistribution(smo2DistributionDouble, smo2Distribution); + doubleArrayForDistribution(wbalDistributionDouble, wbalDistribution); } //---------------------------------------------------------------------- @@ -1294,6 +1308,7 @@ RideFileCache::computeDistribution(QVector &array, RideFile::SeriesType s if (series == RideFile::cadd) needSeries = RideFile::cad; if (series == RideFile::nmd) needSeries = RideFile::nm; if (series == RideFile::hrd) needSeries = RideFile::hr; + if (series == RideFile::wbal) needSeries = RideFile::watts; // only bother if the data series is actually present if (ride->isDataPresent(needSeries) == false) return; @@ -1306,6 +1321,9 @@ RideFileCache::computeDistribution(QVector &array, RideFile::SeriesType s if (zoneRange != -1) CP=context->athlete->zones()->getCP(zoneRange); else CP=0; + if (zoneRange != -1) WPRIME=context->athlete->zones()->getWprime(zoneRange); + else WPRIME=0; + if (hrZoneRange != -1) LTHR=context->athlete->hrZones()->getLT(hrZoneRange); else LTHR=0; @@ -1320,75 +1338,109 @@ RideFileCache::computeDistribution(QVector &array, RideFile::SeriesType s // lets resize the array to the right size // it will also initialise with a default value // which for longs is handily zero - array.resize(max-min); + array.resize(max-min+1); - foreach(RideFilePoint *dp, ride->dataPoints()) { - double value = dp->value(baseSeries); - if (series == RideFile::wattsKg) { - value /= ride->getWeight(); + // wbal uses the wprimeData, not the ridefile + if (series == RideFile::wbal) { + + // set timeinzone to zero + wbalTimeInZone.fill(0.0f, 4); + array.fill(0.0f); + int count = 0; + + // lets count them first then turn into percentages + // after we have traversed all the data + foreach(int value, ride->wprimeData()->ydata()) { + + // percent is PERCENT OF W' USED + double percent = 100.0f - ((double (value) / WPRIME) * 100.0f); + if (percent < 0.0f) percent = 0.0f; + if (percent > 100.0f) percent = 100.0f; + + // increment counts + array[(int)percent]++; + count++; + + // and zones in 1s increments + if (percent <= 25.0f) wbalTimeInZone[0]++; + else if (percent <= 50.0f) wbalTimeInZone[1]++; + else if (percent <= 75.0f) wbalTimeInZone[2]++; + else wbalTimeInZone[3]++; } - float lvalue = value * pow(10, decimals); + // turn array into percents (TiZ stays in seconds) + for(int i=0; i<100; i++) array[i] = array[i] / double(count) * 100.0f; - // watts time in zone - if (series == RideFile::watts && zoneRange != -1) { - int index = context->athlete->zones()->whichZone(zoneRange, dp->value(series)); - if (index >=0) wattsTimeInZone[index] += ride->recIntSecs(); + } else { + + foreach(RideFilePoint *dp, ride->dataPoints()) { + double value = dp->value(baseSeries); + if (series == RideFile::wattsKg) { + value /= ride->getWeight(); + } + + float lvalue = value * pow(10, decimals); + + // watts time in zone + if (series == RideFile::watts && zoneRange != -1) { + int index = context->athlete->zones()->whichZone(zoneRange, dp->value(series)); + if (index >=0) wattsTimeInZone[index] += ride->recIntSecs(); + } + + // Polarized zones :- I(<0.85*CP), II (0.85*CP), III (>CP) + if (series == RideFile::watts && zoneRange != -1 && CP) { + if (dp->value(series) < 1) // I zero watts + wattsCPTimeInZone[0] += ride->recIntSecs(); + else if (dp->value(series) < (CP*0.85f)) // I + wattsCPTimeInZone[1] += ride->recIntSecs(); + else if (dp->value(series) < CP) // II + wattsCPTimeInZone[2] += ride->recIntSecs(); + else // III + wattsCPTimeInZone[3] += ride->recIntSecs(); + } + + // hr time in zone + if (series == RideFile::hr && hrZoneRange != -1) { + int index = context->athlete->hrZones()->whichZone(hrZoneRange, dp->value(series)); + if (index >= 0) hrTimeInZone[index] += ride->recIntSecs(); + } + + // Polarized zones :- I(<0.9*LTHR), II (0.9*LTHR), III (>LTHR) + if (series == RideFile::hr && hrZoneRange != -1 && LTHR) { + if (dp->value(series) < 1) // I zero + hrCPTimeInZone[0] += ride->recIntSecs(); + else if (dp->value(series) < (LTHR*0.9f)) // I + hrCPTimeInZone[1] += ride->recIntSecs(); + else if (dp->value(series) < LTHR) // II + hrCPTimeInZone[2] += ride->recIntSecs(); + else // III + hrCPTimeInZone[3] += ride->recIntSecs(); + } + + // pace time in zone, only for running and swimming activities + if (series == RideFile::kph && paceZoneRange != -1 && (ride->isRun() || ride->isSwim())) { + int index = context->athlete->paceZones(ride->isSwim())->whichZone(paceZoneRange, dp->value(series)); + if (index >= 0) paceTimeInZone[index] += ride->recIntSecs(); + } + + // Polarized zones Run:- I(<0.9*CV), II (0.9*CV), III (>CV) + // Polarized zones Swim:- I(<0.975*CV), II (0.975*CV), III (>CV) + if (series == RideFile::kph && paceZoneRange != -1 && CV && (ride->isRun() || ride->isSwim())) { + if (dp->value(series) < 0.1) // I zero + paceCPTimeInZone[0] += ride->recIntSecs(); + else if (ride->isRun() && dp->value(series) < (CV*0.9f)) // I for run + paceCPTimeInZone[1] += ride->recIntSecs(); + else if (ride->isSwim() && dp->value(series) < (CV*0.975f)) // I for swim + paceCPTimeInZone[1] += ride->recIntSecs(); + else if (dp->value(series) < CV) // II + paceCPTimeInZone[2] += ride->recIntSecs(); + else // III + paceCPTimeInZone[3] += ride->recIntSecs(); + } + + int offset = lvalue - min; + if (offset >= 0 && offset < array.size()) array[offset] += ride->recIntSecs(); } - - // Polarized zones :- I(<0.85*CP), II (0.85*CP), III (>CP) - if (series == RideFile::watts && zoneRange != -1 && CP) { - if (dp->value(series) < 1) // I zero watts - wattsCPTimeInZone[0] += ride->recIntSecs(); - else if (dp->value(series) < (CP*0.85f)) // I - wattsCPTimeInZone[1] += ride->recIntSecs(); - else if (dp->value(series) < CP) // II - wattsCPTimeInZone[2] += ride->recIntSecs(); - else // III - wattsCPTimeInZone[3] += ride->recIntSecs(); - } - - // hr time in zone - if (series == RideFile::hr && hrZoneRange != -1) { - int index = context->athlete->hrZones()->whichZone(hrZoneRange, dp->value(series)); - if (index >= 0) hrTimeInZone[index] += ride->recIntSecs(); - } - - // Polarized zones :- I(<0.9*LTHR), II (0.9*LTHR), III (>LTHR) - if (series == RideFile::hr && hrZoneRange != -1 && LTHR) { - if (dp->value(series) < 1) // I zero - hrCPTimeInZone[0] += ride->recIntSecs(); - else if (dp->value(series) < (LTHR*0.9f)) // I - hrCPTimeInZone[1] += ride->recIntSecs(); - else if (dp->value(series) < LTHR) // II - hrCPTimeInZone[2] += ride->recIntSecs(); - else // III - hrCPTimeInZone[3] += ride->recIntSecs(); - } - - // pace time in zone, only for running and swimming activities - if (series == RideFile::kph && paceZoneRange != -1 && (ride->isRun() || ride->isSwim())) { - int index = context->athlete->paceZones(ride->isSwim())->whichZone(paceZoneRange, dp->value(series)); - if (index >= 0) paceTimeInZone[index] += ride->recIntSecs(); - } - - // Polarized zones Run:- I(<0.9*CV), II (0.9*CV), III (>CV) - // Polarized zones Swim:- I(<0.975*CV), II (0.975*CV), III (>CV) - if (series == RideFile::kph && paceZoneRange != -1 && CV && (ride->isRun() || ride->isSwim())) { - if (dp->value(series) < 0.1) // I zero - paceCPTimeInZone[0] += ride->recIntSecs(); - else if (ride->isRun() && dp->value(series) < (CV*0.9f)) // I for run - paceCPTimeInZone[1] += ride->recIntSecs(); - else if (ride->isSwim() && dp->value(series) < (CV*0.975f)) // I for swim - paceCPTimeInZone[1] += ride->recIntSecs(); - else if (dp->value(series) < CV) // II - paceCPTimeInZone[2] += ride->recIntSecs(); - else // III - paceCPTimeInZone[3] += ride->recIntSecs(); - } - - int offset = lvalue - min; - if (offset >= 0 && offset < array.size()) array[offset] += ride->recIntSecs(); } } @@ -1471,6 +1523,7 @@ RideFileCache::RideFileCache(Context *context, QDate start, QDate end, bool filt wattsKgDistribution.resize(0); aPowerDistribution.resize(0); smo2Distribution.resize(0); + wbalDistribution.resize(0); // time in zone are fixed to 10 zone max wattsTimeInZone.resize(10); @@ -1479,6 +1532,7 @@ RideFileCache::RideFileCache(Context *context, QDate start, QDate end, bool filt hrCPTimeInZone.resize(4); paceTimeInZone.resize(10); paceCPTimeInZone.resize(4); + wbalTimeInZone.resize(4); // set cursor busy whilst we aggregate -- bit of feedback // and less intrusive than a popup box @@ -1535,6 +1589,7 @@ RideFileCache::RideFileCache(Context *context, QDate start, QDate end, bool filt distAggregate(wattsKgDistributionDouble, rideCache.wattsKgDistributionDouble); distAggregate(aPowerDistributionDouble, rideCache.aPowerDistributionDouble); distAggregate(smo2DistributionDouble, rideCache.smo2DistributionDouble); + distAggregate(wbalDistributionDouble, rideCache.wbalDistributionDouble); // cumulate timeinzones for (int i=0; i<10; i++) { @@ -1545,6 +1600,7 @@ RideFileCache::RideFileCache(Context *context, QDate start, QDate end, bool filt paceCPTimeInZone[i] += rideCache.paceCPTimeInZone[i]; hrCPTimeInZone[i] += rideCache.hrCPTimeInZone[i]; wattsCPTimeInZone[i] += rideCache.wattsCPTimeInZone[i]; + wbalTimeInZone[i] += rideCache.wbalTimeInZone[i]; } } } @@ -1616,6 +1672,7 @@ RideFileCache::serialize(QDataStream *out) head.version = RideFileCacheVersion; head.crc = crc; head.CP = CP; + head.WPRIME = WPRIME; head.LTHR = LTHR; head.CV = CV; head.WEIGHT = WEIGHT; @@ -1646,6 +1703,7 @@ RideFileCache::serialize(QDataStream *out) head.wattsKgDistCount = wattsKgDistribution.size(); head.aPowerDistCount = aPowerDistribution.size(); head.smo2DistCount = smo2Distribution.size(); + head.wbalDistCount = wbalDistribution.size(); out->writeRawData((const char *) &head, sizeof(head)); @@ -1678,6 +1736,7 @@ RideFileCache::serialize(QDataStream *out) out->writeRawData((const char *) wattsKgDistribution.data(), sizeof(float) * wattsKgDistribution.size()); out->writeRawData((const char *) aPowerDistribution.data(), sizeof(float) * aPowerDistribution.size()); out->writeRawData((const char *) smo2Distribution.data(), sizeof(float) * smo2Distribution.size()); + out->writeRawData((const char *) wbalDistribution.data(), sizeof(float) * wbalDistribution.size()); // time in zone out->writeRawData((const char *) wattsTimeInZone.data(), sizeof(float) * wattsTimeInZone.size()); @@ -1686,6 +1745,7 @@ RideFileCache::serialize(QDataStream *out) out->writeRawData((const char *) hrCPTimeInZone.data(), sizeof(float) * hrCPTimeInZone.size()); out->writeRawData((const char *) paceTimeInZone.data(), sizeof(float) * paceTimeInZone.size()); out->writeRawData((const char *) paceCPTimeInZone.data(), sizeof(float) * paceCPTimeInZone.size()); + out->writeRawData((const char *) wbalTimeInZone.data(), sizeof(float) * wbalTimeInZone.size()); } void @@ -1727,6 +1787,7 @@ RideFileCache::readCache() wattsKgDistribution.resize(head.wattsKgDistCount); aPowerDistribution.resize(head.aPowerDistCount); smo2Distribution.resize(head.smo2DistCount); + wbalDistribution.resize(head.wbalDistCount); // read in the arrays inFile.readRawData((char *) wattsMeanMax.data(), sizeof(float) * wattsMeanMax.size()); @@ -1758,6 +1819,7 @@ RideFileCache::readCache() inFile.readRawData((char *) wattsKgDistribution.data(), sizeof(float) * wattsKgDistribution.size()); inFile.readRawData((char *) aPowerDistribution.data(), sizeof(float) * aPowerDistribution.size()); inFile.readRawData((char *) smo2Distribution.data(), sizeof(float) * smo2Distribution.size()); + inFile.readRawData((char *) wbalDistribution.data(), sizeof(float) * wbalDistribution.size()); // time in zone inFile.readRawData((char *) wattsTimeInZone.data(), sizeof(float) * 10); @@ -1766,6 +1828,7 @@ RideFileCache::readCache() inFile.readRawData((char *) hrCPTimeInZone.data(), sizeof(float) * 4); inFile.readRawData((char *) paceTimeInZone.data(), sizeof(float) * 10); inFile.readRawData((char *) paceCPTimeInZone.data(), sizeof(float) * 4); + inFile.readRawData((char *) wbalTimeInZone.data(), sizeof(float) * 4); // setup the doubles the users use doubleArray(wattsMeanMaxDouble, wattsMeanMax, RideFile::watts); @@ -1795,6 +1858,7 @@ RideFileCache::readCache() doubleArrayForDistribution(wattsKgDistributionDouble, wattsKgDistribution); doubleArrayForDistribution(aPowerDistributionDouble, aPowerDistribution); doubleArrayForDistribution(smo2DistributionDouble, smo2Distribution); + doubleArrayForDistribution(wbalDistributionDouble, wbalDistribution); cacheFile.close(); } diff --git a/src/RideFileCache.h b/src/RideFileCache.h index 4b8c0b80c..ce96d807c 100644 --- a/src/RideFileCache.h +++ b/src/RideFileCache.h @@ -41,7 +41,7 @@ typedef double data_t; // arrays when plotting CP curves and histograms. It is precoputed // to save time and cached in a file .cpx // -static const unsigned int RideFileCacheVersion = 22; +static const unsigned int RideFileCacheVersion = 23; // revision history: // version date description // 1 29-Apr-11 Initial - header, mean-max & distribution data blocks @@ -65,12 +65,14 @@ static const unsigned int RideFileCacheVersion = 22; // 20 17-Nov-14 Added Polarized Zones for HR and Pace // 21 27-Nov-14 Added SmO2 distribution // 22 02-Feb-15 Added weight to header +// 23 14-Jun-15 Added W'bal TiZ and Distribution // The cache file (.cpx) has a binary format: // 1 x Header data - describing the version and contents of the cache // n x Blocks - meanmax or distribution arrays // 1 x Watts TIZ - 10 floats // 1 x Heartrate TIZ - 10 floats +// 1 x W'Bal TIX - 10 floats // The header is written directly to disk, the only // field which is endian sensitive is the count field @@ -106,12 +108,14 @@ struct RideFileCacheHeader { npDistCount, wattsKgDistCount, aPowerDistCount, - smo2DistCount; + smo2DistCount, + wbalDistCount; int LTHR, // used to calculate Time in Zone (TIZ) CP; // used to calculate Time in Zone (TIZ) double CV; // used to calculate Time in Zone (TIZ) double WEIGHT; // weight in kg x 10 used for w/kg + double WPRIME; // W' used from config used to calculate (TIZ) }; @@ -232,6 +236,7 @@ class RideFileCache // used for zoning int CP; + int WPRIME; int LTHR; double CV; double WEIGHT; @@ -314,6 +319,7 @@ class RideFileCache QVector wattsKgDistribution; // RideFile::wattsKg QVector aPowerDistribution; // RideFile::aPower QVector smo2Distribution; // RideFile::smo2 + QVector wbalDistribution; // RideFile::wbal QVector wattsDistributionDouble; // RideFile::watts QVector hrDistributionDouble; // RideFile::hr @@ -326,6 +332,7 @@ class RideFileCache QVector wattsKgDistributionDouble; // RideFile::wattsKg QVector aPowerDistributionDouble; // RideFile::aPower QVector smo2DistributionDouble; // RideFile::aPower + QVector wbalDistributionDouble; // RideFile::aPower QVector wattsTimeInZone; // time in zone in seconds @@ -334,6 +341,7 @@ class RideFileCache QVector hrCPTimeInZone; // time in zone in seconds for polarized zones QVector paceTimeInZone; // time in zone in seconds QVector paceCPTimeInZone; // time in zone in seconds for polarized zones + QVector wbalTimeInZone; // time in zone in seconds }; // Ride Bests in an associative array