This patch adds an air density (rho) calculator feature to GoldenCheetah.

Users supply the estimator with measurements of temperature,
air pressure, and dew point, and the estimator uses Herman Wobus'
model for vapor pressure, plus the standard equations for
calculating air density, to estimate Rho. This feature is
useful for dialing in the Rho value in aerolab.

The patch hooks the estimator into the Tools menu as another dialog
box, similar to the CP estimator that's also under the tools menu.
This commit is contained in:
Steven Gribble
2012-01-25 20:52:51 -08:00
committed by Gareth Coco
parent 9ed9f7a6d0
commit 03a221c558
5 changed files with 358 additions and 0 deletions

View File

@@ -67,6 +67,7 @@
#include "RideCalendar.h"
#include "DatePickerDialog.h"
#include "ToolsDialog.h"
#include "ToolsRhoEstimator.h"
#include "MetricAggregator.h"
#include "SplitRideDialog.h"
#include "PerformanceManagerWindow.h"
@@ -415,6 +416,8 @@ MainWindow::MainWindow(const QDir &home) :
SLOT(showOptions()), tr("Ctrl+O"));
optionsMenu->addAction(tr("Critical Power Calculator"), this,
SLOT(showTools()));
optionsMenu->addAction(tr("Air Density (Rho) Estimator"), this,
SLOT(showRhoEstimator()));
//optionsMenu->addAction(tr("&Reset Metrics..."), this,
// SLOT(importRideToDB()), tr("Ctrl+R"));
//optionsMenu->addAction(tr("&Update Metrics..."), this,
@@ -1420,6 +1423,12 @@ void MainWindow::showTools()
td->show();
}
void MainWindow::showRhoEstimator()
{
ToolsRhoEstimator *tre = new ToolsRhoEstimator();
tre->show();
}
void
MainWindow::saveRide()
{

View File

@@ -155,6 +155,7 @@ class MainWindow : public QMainWindow
void saveNotes();
void showOptions();
void showTools();
void showRhoEstimator();
void importRideToDB();
void scanForMissing();
void saveAndOpenNotes();

289
src/ToolsRhoEstimator.cpp Normal file
View File

@@ -0,0 +1,289 @@
/*
* Copyright (c) Steven Gribble (gribble [at] gmail [dot] com)
* http://www.gribble.org/
*
* 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 "ToolsRhoEstimator.h"
#include "Settings.h"
#include "Units.h"
#include <QtGui>
#include <sstream>
#include <cmath>
typedef QDoubleSpinBox* QDoubleSpinBoxPtr;
ToolsRhoEstimator::ToolsRhoEstimator(QWidget *parent) : QDialog(parent) {
// Does the user prefer metric or imperial? Set the initial radio
// button and field settings to their GoldenCheetah preference.
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant unit = settings->value(GC_UNIT);
useMetricUnits = (unit.toString() == "Metric");
// Set the main window title.
setWindowTitle(tr("Air Density (Rho) Estimator"));
setAttribute(Qt::WA_DeleteOnClose);
// Create the main layout box.
QVBoxLayout *mainVBox = new QVBoxLayout(this);
// Set up the instructions field.
mainVBox->addWidget(new QLabel(
tr("Enter measured values for the following:")));
mainVBox->addSpacing(5);
// "metric vs. imperial" radio buttons. (Makes it easier than
// forcing them to change their preference in the preferences menu.)
QHBoxLayout *rads = new QHBoxLayout;
metBut = new QRadioButton(tr("Metric"));
metBut->setChecked(useMetricUnits);
rads->addWidget(metBut);
impBut = new QRadioButton(tr("Imperial"));
impBut->setChecked(!useMetricUnits);
// note that we only need to connect one of the radio button
// signals, since changing one also changes the other.
connect(impBut, SIGNAL(toggled(bool)),
this, SLOT(on_radio_toggled(bool)));
rads->addWidget(impBut);
mainVBox->addLayout(rads);
// The temperature box.
QHBoxLayout *thl = new QHBoxLayout;
tempSpinBox = new QDoubleSpinBox(this);
tempSpinBox->setDecimals(2);
tempSpinBox->setRange(-200, 200);
if (useMetricUnits) {
tempLabel = new QLabel("Temperature (C):");
thl->addWidget(tempLabel);
tempSpinBox->setValue(15);
} else {
tempLabel = new QLabel("Temperature (F):");
thl->addWidget(tempLabel);
tempSpinBox->setValue(59);
}
tempSpinBox->setWrapping(false);
tempSpinBox->setAlignment(Qt::AlignRight);
connect(tempSpinBox, SIGNAL(valueChanged(double)),
this, SLOT(on_valueChanged(double)));
thl->addWidget(tempSpinBox);
mainVBox->addLayout(thl);
// The air pressure box.
QHBoxLayout *phl = new QHBoxLayout;
pressSpinBox = new QDoubleSpinBox(this);
pressSpinBox->setDecimals(2);
pressSpinBox->setRange(0, 2000);
if (useMetricUnits) {
pressLabel = new QLabel("Air Pressure (hPa):");
phl->addWidget(pressLabel);
pressSpinBox->setValue(1018);
} else {
pressLabel = new QLabel("Air Pressure (inHg):");
phl->addWidget(pressLabel);
pressSpinBox->setValue(30.06);
}
pressSpinBox->setWrapping(false);
pressSpinBox->setAlignment(Qt::AlignRight);
connect(pressSpinBox, SIGNAL(valueChanged(double)),
this, SLOT(on_valueChanged(double)));
phl->addWidget(pressSpinBox);
mainVBox->addLayout(phl);
// The dewpoint box.
QHBoxLayout *dhl = new QHBoxLayout;
dewpSpinBox = new QDoubleSpinBox(this);
dewpSpinBox->setDecimals(2);
dewpSpinBox->setRange(-200, 200);
if (useMetricUnits) {
dewpLabel = new QLabel("Dewpoint (C):");
dhl->addWidget(dewpLabel);
dewpSpinBox->setValue(7.5);
} else {
dewpLabel = new QLabel("Dewpoint (F):");
dhl->addWidget(dewpLabel);
dewpSpinBox->setValue(45.5);
}
dewpSpinBox->setWrapping(false);
dewpSpinBox->setAlignment(Qt::AlignRight);
connect(dewpSpinBox, SIGNAL(valueChanged(double)),
this, SLOT(on_valueChanged(double)));
dhl->addWidget(dewpSpinBox);
mainVBox->addLayout(dhl);
// Label for the computed air density.
mainVBox->addSpacing(15);
mainVBox->addWidget(new QLabel(
tr("Estimated air density (rho):")));
// Estimated Rho (metric units).
QHBoxLayout *rhoMetHBox = new QHBoxLayout;
txtRhoMet = new QLineEdit(this);
txtRhoMet->setAlignment(Qt::AlignRight);
txtRhoMet->setReadOnly(true);
rhoMetHBox->addWidget(txtRhoMet);
rhoMetHBox->addWidget(new QLabel(tr("(kg/m^3)")));
mainVBox->addLayout(rhoMetHBox);
// Estimated Rho (imperial units).
QHBoxLayout *rhoImpHBox = new QHBoxLayout;
txtRhoImp = new QLineEdit(this);
txtRhoImp->setAlignment(Qt::AlignRight);
txtRhoImp->setReadOnly(true);
rhoImpHBox->addWidget(txtRhoImp);
rhoImpHBox->addWidget(new QLabel(tr("(lb/ft^3)")));
mainVBox->addLayout(rhoImpHBox);
// "Done" button.
mainVBox->addSpacing(15);
QHBoxLayout *buttonHBox = new QHBoxLayout;
btnOK = new QPushButton(this);
btnOK->setText(tr("Done"));
buttonHBox->addWidget(btnOK);
mainVBox->addLayout(buttonHBox);
connect(btnOK, SIGNAL(clicked()), this, SLOT(on_btnOK_clicked()));
// Force the initial "rho" calculation when the dialog box comes up
// initially.
this->on_valueChanged(0.0);
}
void ToolsRhoEstimator::on_radio_toggled(bool checked) {
checked = true; // hack to avoid the "unused parameter" g++ warning
if (impBut->isChecked()) {
// we just changed from metric --> imperial, so relabel and do the
// field conversions for the user.
tempSpinBox->setValue(celsius_to_fahrenheit(tempSpinBox->value()));
tempLabel->setText("Temperature (F):");
pressSpinBox->setValue(
hectopascals_to_inchesmercury(pressSpinBox->value()));
pressLabel->setText("Air Pressure (inHg):");
dewpSpinBox->setValue(celsius_to_fahrenheit(dewpSpinBox->value()));
dewpLabel->setText("Dewpoint (F):");
} else {
// we just changed from imperial --> metric, so relabel and do the
// field conversions for the user.
tempSpinBox->setValue(fahrenheit_to_celsius(tempSpinBox->value()));
tempLabel->setText("Temperature (C):");
pressSpinBox->setValue(
inchesmercury_to_hectopascals(pressSpinBox->value()));
pressLabel->setText("Air Pressure (hPa):");
dewpSpinBox->setValue(fahrenheit_to_celsius(dewpSpinBox->value()));
dewpLabel->setText("Dewpoint (C):");
}
// Relay the "something has changed" signal to
// on_valueChanged(double).
this->on_valueChanged(0.0);
}
void ToolsRhoEstimator::on_btnOK_clicked() {
// all done!
accept();
}
void ToolsRhoEstimator::on_valueChanged(double newval) {
newval++; // hack to avoid the "unused parameter" g++ warning.
// scrape the field values, convert to metric if needed.
double temp = tempSpinBox->value();
double press = pressSpinBox->value();
double dewp = dewpSpinBox->value();
if (impBut->isChecked()) {
// yup, convert to metric.
temp = fahrenheit_to_celsius(temp);
press = inchesmercury_to_hectopascals(press);
dewp = fahrenheit_to_celsius(dewp);
}
// calculate rho, in (kg/m^3)
double rho_met = calculate_rho(temp, press, dewp);
// calculate rho in imperial units.
double rho_imp = rho_met_to_imp(rho_met);
// display the calculated Rho's.
std::stringstream ss_met, ss_imp;
ss_met.precision(6);
ss_met << rho_met;
txtRhoMet->setText(tr(ss_met.str().c_str()));
ss_imp.precision(6);
ss_imp << rho_imp;
txtRhoImp->setText(tr(ss_imp.str().c_str()));
}
double ToolsRhoEstimator::fahrenheit_to_celsius(double f) {
return (f - 32.0) * (5.0/9.0);
}
double ToolsRhoEstimator::celsius_to_fahrenheit(double c) {
return (c * 9.0 / 5.0) + 32.0;
}
double ToolsRhoEstimator::hectopascals_to_inchesmercury(double hpa) {
return (hpa / 1000.0) * 29.53;
}
double ToolsRhoEstimator::inchesmercury_to_hectopascals(double inhg) {
return (inhg * 1000.0) / 29.53;
}
double ToolsRhoEstimator::rho_met_to_imp(double rho) {
return rho * ((0.30480061 * 0.30480061 * 0.30480061) / 0.45359237);
}
double ToolsRhoEstimator::calculate_rho(double temp,
double press,
double dewp) {
// Step 1: calculate the saturation water pressure at the dew point,
// i.e., the vapor pressure in the air. Use Herman Wobus' equation.
double c0 = 0.99999683;
double c1 = -0.90826951E-02;
double c2 = 0.78736169E-04;
double c3 = -0.61117958E-06;
double c4 = 0.43884187E-08;
double c5 = -0.29883885E-10;
double c6 = 0.21874425E-12;
double c7 = -0.17892321E-14;
double c8 = 0.11112018E-16;
double c9 = -0.30994571E-19;
double p = c0 +
dewp*(c1 +
dewp*(c2 +
dewp*(c3 +
dewp*(c4 +
dewp*(c5 +
dewp*(c6 +
dewp*(c7 +
dewp*(c8 +
dewp*(c9)))))))));
double psat_mbar = 6.1078 / std::pow(p, 8);
// Step 2: calculate the vapor pressure.
double pv_pascals = psat_mbar * 100.0;
// Step 3: calculate the pressure of dry air, given the vapor
// pressure and actual pressure.
double pd_pascals = (press*100) - pv_pascals;
// Step 4: calculate the air density, using the equation for
// the density of a mixture of dry air and water vapor.
double density =
(pd_pascals / (287.0531 * (temp + 273.15))) +
(pv_pascals / (461.4964 * (temp + 273.15)));
return density;
}

57
src/ToolsRhoEstimator.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2012 Steven Gribble (gribble [at] gmail [dot] com)
* http://www.gribble.org/
*
* 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 <QDateTime>
#include <QtGui>
// This class implements a dialog box containing a tool for
// helping the user estimate rho, the air density, given
// temperature, pressure, and dew point as inputs.
class ToolsRhoEstimator : public QDialog {
Q_OBJECT
public:
ToolsRhoEstimator(QWidget *parent = 0);
private:
bool useMetricUnits;
QRadioButton *metBut;
QRadioButton *impBut;
QPushButton *btnOK;
QLineEdit *txtRhoImp;
QLineEdit *txtRhoMet;
QLabel *tempLabel;
QLabel *pressLabel;
QLabel *dewpLabel;
QDoubleSpinBox *tempSpinBox;
QDoubleSpinBox *pressSpinBox;
QDoubleSpinBox *dewpSpinBox;
double fahrenheit_to_celsius(double f);
double celsius_to_fahrenheit(double c);
double hectopascals_to_inchesmercury(double hpa);
double inchesmercury_to_hectopascals(double inhg);
double rho_met_to_imp(double rho);
double calculate_rho(double temp, double press, double dewp);
private slots:
void on_radio_toggled(bool checked);
void on_btnOK_clicked();
void on_valueChanged(double newval);
};

View File

@@ -193,6 +193,7 @@ HEADERS += \
TxtRideFile.h \
TimeUtils.h \
ToolsDialog.h \
ToolsRhoEstimator.h \
TrainTabs.h \
TrainTool.h \
TrainWindow.h \
@@ -322,6 +323,7 @@ SOURCES += \
TimeInZone.cpp \
TimeUtils.cpp \
ToolsDialog.cpp \
ToolsRhoEstimator.cpp \
TrainTabs.cpp \
TrainTool.cpp \
TrainWindow.cpp \