From d4aebbb4417e85021dbf906c0a195ccbfd91ec26 Mon Sep 17 00:00:00 2001 From: ericchristoffersen <46055145+ericchristoffersen@users.noreply.github.com> Date: Fri, 13 Nov 2020 04:05:28 -0800 Subject: [PATCH] Fix 3596: Fix display of route elevation. (#3664) Simplified the meterwidget elevation display loop, bug was that it was skipping the final route point. Add minY to ergfile so range of y can be obtained without computing each time, so can remove search loop from meterwidget. --- src/Train/ErgFile.cpp | 54 +++++++++++++++++----------- src/Train/ErgFile.h | 12 +++---- src/Train/MeterWidget.cpp | 74 ++++++++++++++++++++++----------------- src/Train/MeterWidget.h | 4 +-- 4 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/Train/ErgFile.cpp b/src/Train/ErgFile.cpp index a33bdce82..660e7179f 100644 --- a/src/Train/ErgFile.cpp +++ b/src/Train/ErgFile.cpp @@ -1381,7 +1381,7 @@ ErgFile::~ErgFile() } bool -ErgFile::isValid() +ErgFile::isValid() const { return valid; } @@ -1617,12 +1617,12 @@ int ErgFile::nextText(long x) void ErgFile::calculateMetrics() { - // reset metrics XP = CP = AP = IsoPower = IF = RI = BikeStress = BS = SVI = VI = 0; ELE = ELEDIST = GRADE = 0; - maxY = 0; // we need to reset it + minY = 0; + maxY = 0; // is it valid? if (!isValid()) return; @@ -1631,24 +1631,29 @@ ErgFile::calculateMetrics() ErgFilePoint last; bool first = true; - foreach (ErgFilePoint p, Points) { - - // set the maximum Y value - if (p.y > maxY) maxY= p.y; + foreach(ErgFilePoint p, Points) { if (first == true) { + minY = p.y; + maxY = p.y; first = false; - } else if (p.y > last.y) { + } + else { + minY = std::min(minY, p.y); + maxY = std::max(maxY, p.y); - ELEDIST += p.x - last.x; - ELE += p.y - last.y; + if (p.y > last.y) { + ELEDIST += p.x - last.x; + ELE += p.y - last.y; + } } last = p; } if (ELE == 0 || ELEDIST == 0) GRADE = 0; - else GRADE = ELE/ELEDIST * 100; + else GRADE = ELE / ELEDIST * 100; - } else { + } + else { QVector rolling(30); rolling.fill(0.0f); @@ -1675,28 +1680,37 @@ ErgFile::calculateMetrics() int skcount = 0; ErgFilePoint last; - foreach (ErgFilePoint p, Points) { + bool first = true; + foreach(ErgFilePoint p, Points) { - // set the maximum Y value - if (p.y > maxY) maxY= p.y; + // set the minimum/maximum Y value + if (first) { + minY = p.y; + maxY = p.y; + first = false; + } + else { + minY = std::min(minY, p.y); + maxY = std::min(maxY, p.y); + } while (nextSecs < p.x) { // CALCULATE IsoPower apsum += last.y; - sum += last.y; + sum += last.y; sum -= rolling[index]; // update 30s circular buffer rolling[index] = last.y; - if (index == 29) index=0; + if (index == 29) index = 0; else index++; - total += pow(sum/30, 4); - count ++; + total += pow(sum / 30, 4); + count++; // CALCULATE XPOWER - while ((weighted > NEGLIGIBLE) && ((nextSecs / 1000) > (lastSecs/1000) + 1000 + EPSILON)) { + while ((weighted > NEGLIGIBLE) && ((nextSecs / 1000) > (lastSecs / 1000) + 1000 + EPSILON)) { weighted *= attenuation; lastSecs += 1000; sktotal += pow(weighted, 4.0); diff --git a/src/Train/ErgFile.h b/src/Train/ErgFile.h index 82c3ce291..600e5ac7e 100644 --- a/src/Train/ErgFile.h +++ b/src/Train/ErgFile.h @@ -118,7 +118,7 @@ class ErgFile void parseErg2(QString p = ""); // ergdb void parseTTS(); // its ahh tts - bool isValid(); // is the file valid or not? + bool isValid() const; // is the file valid or not? double Cp; int format; // ERG, CRS, MRC, ERG2 currently supported @@ -150,7 +150,7 @@ class ErgFile int MaxWatts; // maxWatts in this ergfile (scaling) bool valid; // did it parse ok? int mode; - bool StrictGradient; // should gradient be strict or smoothed? + bool StrictGradient; // should gradient be strict or smoothed? int leftPoint, rightPoint; // current points we are between int interpolatorReadIndex; // next point to be fed to interpolator @@ -159,15 +159,15 @@ class ErgFile QList Laps; // interval markers in the file QList Texts; // texts to display - GeoPointInterpolator gpi; // Location interpolator + GeoPointInterpolator gpi; // Location interpolator - void calculateMetrics(); // calculate IsoPower value for ErgFile + void calculateMetrics(); // calculate IsoPower value for ErgFile // Metrics for this workout - double maxY; // maximum Y value + double minY, maxY; // minimum and maximum Y value double CP; double AP, IsoPower, IF, BikeStress, VI; // Coggan for erg / mrc - double XP, RI, BS, SVI; // Skiba for erg / mrc + double XP, RI, BS, SVI; // Skiba for erg / mrc double ELE, ELEDIST, GRADE; // crs Context *context; diff --git a/src/Train/MeterWidget.cpp b/src/Train/MeterWidget.cpp index 346ea6219..0ad41d463 100644 --- a/src/Train/MeterWidget.cpp +++ b/src/Train/MeterWidget.cpp @@ -353,52 +353,62 @@ void NeedleMeterWidget::paintEvent(QPaintEvent* paintevent) } ElevationMeterWidget::ElevationMeterWidget(QString Name, QWidget *parent, QString Source, Context *context) : MeterWidget(Name, parent, Source), context(context), - m_minX(0.), m_maxX(0.), m_savedWidth(0), m_savedHeight(0), gradientValue(0.) + m_minX(0.), m_maxX(0.), m_savedWidth(0), m_savedHeight(0), m_savedMinY(0), m_savedMaxY(0), gradientValue(0.) { forceSquareRatio = false; } // Compute polygon for elevation graph based on widget size. -// This is two full scans of the ergfile point array. -void ElevationMeterWidget::lazyDimensionCompute(void) +void ElevationMeterWidget::lazySetup(void) { - // Nothing to compute unless there is an erg file. - if (!context || !context->currentErgFile()) + // Nothing to compute unless there is a valid erg file. + if (!context) return; - // Compute if size has changed - if (m_savedWidth != m_Width || m_savedHeight != m_Height) { + const ErgFile* ergFile = context->currentErgFile(); + if (!ergFile || !ergFile->isValid()) + return; - // Determine extents of route - double minX, minY, maxX, maxY; - minX = maxX = context->currentErgFile()->Points[0].x; // meters - minY = maxY = context->currentErgFile()->Points[0].y; // meters or altitude??? - foreach(ErgFilePoint x, context->currentErgFile()->Points) { - minX = std::min(minX, x.x); - minY = std::min(minY, x.y); - maxX = std::max(maxX, x.x); - maxY = std::max(maxY, x.y); - } + // Compute if size has changed. Store truncated values to allow equality comparison. + double minX = 0.; + double maxX = floor(ergFile->Duration); + double minY = floor(ergFile->minY); + double maxY = floor(ergFile->maxY); + if (m_savedWidth != m_Width || m_savedHeight != m_Height || m_savedMinY != minY || m_savedMaxY != maxY) { - if (m_Width != 0 && (maxY - minY) / 0.05 < (double)m_Height * 0.80 * (maxX - minX) / (double)m_Width) - maxY = minY + (double)m_Height * 0.80 * (maxX - minX) / (double)m_Width * 0.05; + m_savedMinY = minY; + m_savedMaxY = maxY; + + if (m_Width != 0 && (maxY - minY) / 0.05 < m_Height * 0.80 * (maxX - minX) / m_Width) + maxY = minY + m_Height * 0.80 * (maxX - minX) / m_Width * 0.05; minY -= (maxY - minY) * 0.20f; // add 20% as bottom headroom (slope gradient will be shown there in a bubble) // Populate elevation route polygon m_elevationPolygon.clear(); - m_elevationPolygon << QPoint(0.0, (double)m_Height); - double x = 0, y = 0; - double nextX = 1; - for (double pt=0; pt < context->currentErgFile()->Points.size(); pt++) { - for (; x < nextX && pt < context->currentErgFile()->Points.size(); pt++) { - x = (context->currentErgFile()->Points[pt].x - minX) * (double)m_Width / (maxX - minX); - y = (context->currentErgFile()->Points[pt].y - minY) * (double)m_Height / (maxY - minY); - } - // Add points to polygon only once every time the x coordinate integer part changes. - m_elevationPolygon << QPoint(x, (double)m_Height - y); - nextX = floor(x) + 1.0; + m_elevationPolygon << QPoint(0.0, m_Height); + + // Scaling Multiples. + const double xScale = m_Width / (maxX - minX); + const double yScale = m_Height / (maxY - minY); + + int lastPixelX = -1; // always draw first point. + foreach(const ErgFilePoint &p, ergFile->Points) { + + int pixelX = (int)floor((p.x - minX) * xScale); + + // Skip over segment points that are less than a pixel to the right of the last segment we drew. + if (pixelX == lastPixelX) + continue; + + int pixelY = (int)(m_Height - floor((p.y - minY) * yScale)); + + m_elevationPolygon << QPoint(pixelX, pixelY); + + lastPixelX = pixelX; } - m_elevationPolygon << QPoint((double)m_Width, (double)m_Height); + + // Complete a final segment from elevation profile to bottom right of display rect. + m_elevationPolygon << QPoint(m_Width, m_Height); // Save distance extent, used to situate rider location within widget display. m_minX = minX; @@ -431,7 +441,7 @@ void ElevationMeterWidget::paintEvent(QPaintEvent* paintevent) // Lazy compute of min, max and route elevation polygon on init or if dimensions // change. Ideally we'd use the resize event but we are computing from ergfile // which isnt available when initial resize occurs. - lazyDimensionCompute(); + lazySetup(); double bubbleSize = (double)m_Height * 0.010f; diff --git a/src/Train/MeterWidget.h b/src/Train/MeterWidget.h index 1ca12b660..261210db8 100644 --- a/src/Train/MeterWidget.h +++ b/src/Train/MeterWidget.h @@ -124,12 +124,12 @@ class ElevationMeterWidget : public MeterWidget Context* context; QPolygon m_elevationPolygon; double m_minX, m_maxX; - int m_savedWidth, m_savedHeight; + int m_savedWidth, m_savedHeight, m_savedMinY, m_savedMaxY; protected: virtual void paintEvent(QPaintEvent* paintevent); - void lazyDimensionCompute(void); + void lazySetup(void); public: explicit ElevationMeterWidget(QString name, QWidget *parent = 0, QString Source = QString("None"), Context *context = NULL);