introduce "metric overrides"

This commit allows every ride file to specify a set of "metric overrides":
values to use in place of those for RideMetrics we would otherwise compute.

The most gratifying immediate result of this change is that we can associate a
"skiba_bike_score" metric override with each Manual CSV file, thereby
eliminating the need for a bogus "bs" parameter in RideFilePoint.

In the future, though, we can also save these overrides to a GcRideFile using
a syntax something like this:

  <override>
    <metric name="skiba_bike_score" value="100"/>
    <metric name="average_speed" secs="3600" km="30"/>
  </override>

(Note that average_speed needs to store time and distance in order to
aggregate properly.)

Then we can add a dialog that allows the user to override the computed value
of a metric for any given ride.  For example, if my HRM was on the fritz
during a ride, I could estimate my average HR and override that metric.
(We might want to show these overrided metrics in a different color, so that
it was clear they weren't the computed values.)

Finally, I think we could actually use this feature to eliminate the Manual
CSV format altogether, and just use GcRideFiles without any samples or
intervals, but with metric overrides for all the available metrics.
This commit is contained in:
Sean Rhea
2009-12-17 18:49:13 -05:00
parent 0dae2b88cc
commit e5affbbc64
7 changed files with 42 additions and 31 deletions

View File

@@ -143,25 +143,23 @@ class BikeScore : public RideMetric {
QString name() const { return "skiba_bike_score"; }
QString units(bool) const { return ""; }
double value(bool) const { return score; }
void compute(const RideFile *ride, const Zones *zones, int zoneRange,
void compute(const RideFile *, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &deps) {
if (!zones || zoneRange < 0)
return;
if (ride->deviceType() == QString("Manual CSV")) {
// manual entry, use BS from dataPoints
score = ride->dataPoints().first()->bs;
}
else {
assert(deps.contains("skiba_xpower"));
assert(deps.contains("skiba_relative_intensity"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
RideMetric *ri = deps.value("skiba_relative_intensity");
assert(ri);
double normWork = xp->xpower * xp->secs;
double rawBikeScore = normWork * ri->value(true);
double workInAnHourAtCP = zones->getCP(zoneRange) * 3600;
score = rawBikeScore / workInAnHourAtCP * 100.0;
}
assert(deps.contains("skiba_xpower"));
assert(deps.contains("skiba_relative_intensity"));
XPower *xp = dynamic_cast<XPower*>(deps.value("skiba_xpower"));
RideMetric *ri = deps.value("skiba_relative_intensity");
assert(ri);
double normWork = xp->xpower * xp->secs;
double rawBikeScore = normWork * ri->value(true);
double workInAnHourAtCP = zones->getCP(zoneRange) * 3600;
score = rawBikeScore / workInAnHourAtCP * 100.0;
}
void override(const QMap<QString,QString> &map) {
if (map.contains("value"))
score = map.value("value").toDouble();
}
RideMetric *clone() const { return new BikeScore(*this); }
bool canAggregate() const { return true; }

View File

@@ -52,7 +52,14 @@ class DanielsPoints : public RideMetric {
// Manual entry: use BS from dataPoints with a scaling factor
// that works about right for long, steady rides.
double scaling_factor = 0.55;
score = ride->dataPoints().first()->bs * scaling_factor;
if (ride->metricOverrides.contains("skiba_bike_score")) {
const QMap<QString,QString> bsm =
ride->metricOverrides.value("skiba_bike_score");
if (bsm.contains("value")) {
double bs = bsm.value("value").toDouble();
score = bs * scaling_factor;
}
}
return;
}

View File

@@ -109,8 +109,11 @@ RideFile *ManualFileReader::openRideFile(QFile &file, QStringList &errors) const
cad = nm = 0.0;
interval = 0;
rideFile->appendPoint(minutes * 60.0, cad, hr, km,
kph, nm, watts, alt, 0.0, 0.0, interval, bs);
rideFile->appendPoint(minutes * 60.0, cad, hr, km,
kph, nm, watts, alt, 0.0, 0.0, interval);
QMap<QString,QString> bsm;
bsm.insert("value", QString("%1").arg(bs));
rideFile->metricOverrides.insert("skiba_bike_score", bsm);
rideSec = minutes * 60.0;
}

View File

@@ -123,10 +123,6 @@ void RideFile::writeAsCsv(QFile &file, bool bIsMetric) const
out << point->interval;
out << ",";
out << point->alt;
if (point->bs > 0.0) {
out << ",";
out << point->bs;
}
out << "\n";
}
@@ -200,10 +196,10 @@ QStringList RideFileFactory::listRideFiles(const QDir &dir) const
void RideFile::appendPoint(double secs, double cad, double hr, double km,
double kph, double nm, double watts, double alt,
double lon, double lat, int interval, double bs)
double lon, double lat, int interval)
{
dataPoints_.append(new RideFilePoint(secs, cad, hr, km, kph,
nm, watts, alt, lon, lat, interval,bs));
nm, watts, alt, lon, lat, interval));
dataPresent.secs |= (secs != 0);
dataPresent.cad |= (cad != 0);
dataPresent.hr |= (hr != 0);

View File

@@ -45,13 +45,12 @@ struct RideFilePoint
{
double secs, cad, hr, km, kph, nm, watts, alt, lon, lat;;
int interval;
double bs; // to init in order
RideFilePoint() : secs(0.0), cad(0.0), hr(0.0), km(0.0), kph(0.0),
nm(0.0), watts(0.0), alt(0.0), lon(0.0), lat(0.0), interval(0), bs(0.0) {}
nm(0.0), watts(0.0), alt(0.0), lon(0.0), lat(0.0), interval(0) {}
RideFilePoint(double secs, double cad, double hr, double km, double kph,
double nm, double watts, double alt, double lon, double lat, int interval, double bs) :
double nm, double watts, double alt, double lon, double lat, int interval) :
secs(secs), cad(cad), hr(hr), km(km), kph(kph), nm(nm),
watts(watts), alt(alt), lon(lon), lat(lat), interval(interval), bs(bs) {}
watts(watts), alt(alt), lon(lon), lat(lat), interval(interval) {}
};
struct RideFileDataPresent
@@ -107,7 +106,7 @@ class RideFile
void appendPoint(double secs, double cad, double hr, double km,
double kph, double nm, double watts, double alt,
double lon, double lat, int interval, double bs=0.0);
double lon, double lat, int interval);
const QList<RideFileInterval> &intervals() const { return intervals_; }
void addInterval(double start, double stop, const QString &name) {
@@ -122,6 +121,8 @@ class RideFile
void resetDataPresent();
double timeToDistance(double) const; // get distance in km at time in secs
QMap<QString,QMap<QString,QString> > metricOverrides;
};
struct RideFileReader {

View File

@@ -171,7 +171,10 @@ RideItem::computeMetrics()
if (!metrics.contains(deps[j]))
goto later;
RideMetric *metric = factory.newMetric(name);
metric->compute(ride(), zones, zone_range, metrics);
if (ride()->metricOverrides.contains(name))
metric->override(ride()->metricOverrides.value(name));
else
metric->compute(ride(), zones, zone_range, metrics);
metrics.insert(name, metric);
i.remove();
}

View File

@@ -37,6 +37,9 @@ struct RideMetric {
const Zones *zones,
int zoneRange,
const QHash<QString,RideMetric*> &deps) = 0;
virtual void override(const QMap<QString,QString> &) {
assert(false);
}
virtual bool canAggregate() const { return false; }
virtual void aggregateWith(RideMetric *other) {
(void) other;