added a new air density (rho) estimator dialog box, hooked it into the Tools menu, and updated src.pro to add compilation dependencies for it

This commit is contained in:
Steven Gribble
2012-01-08 08:53:46 -08:00
committed by Mark Liversedge
parent c0ba84cdbc
commit 5c8beee46d
5 changed files with 359 additions and 0 deletions

View File

@@ -50,6 +50,7 @@
#include "RideCalendar.h"
#include "DatePickerDialog.h"
#include "ToolsDialog.h"
#include "ToolsRhoEstimator.h"
#include "MetricAggregator.h"
#include "SplitActivityWizard.h"
#include "BatchExportDialog.h"
@@ -610,6 +611,7 @@ MainWindow::MainWindow(const QDir &home) :
QMenu *optionsMenu = menuBar()->addMenu(tr("&Tools"));
optionsMenu->addAction(tr("&Options..."), this, 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->addSeparator();
optionsMenu->addAction(tr("Workout Wizard"), this, SLOT(showWorkoutWizard()));
@@ -1075,6 +1077,12 @@ void MainWindow::showTools()
td->show();
}
void MainWindow::showRhoEstimator()
{
ToolsRhoEstimator *tre = new ToolsRhoEstimator();
tre->show();
}
void MainWindow::showWorkoutWizard()
{
WorkoutWizard *ww = new WorkoutWizard(this);

View File

@@ -245,6 +245,7 @@ class MainWindow : public QMainWindow
bool saveRideExitDialog(); // save dirty rides on exit dialog
void showOptions();
void showTools();
void showRhoEstimator();
void toggleSidebar();
void showSidebar(bool want);
void showToolbar(bool want);

288
src/ToolsRhoEstimator.cpp Normal file
View File

@@ -0,0 +1,288 @@
/*
* 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.
QVariant unit = appsettings->value(NULL, 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;
}

60
src/ToolsRhoEstimator.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* 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 "GoldenCheetah.h"
#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
G_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

@@ -328,6 +328,7 @@ HEADERS += \
TxtRideFile.h \
TimeUtils.h \
ToolsDialog.h \
ToolsRhoEstimator.h \
TrainTool.h \
TreeMapWindow.h \
TreeMapPlot.h \
@@ -506,6 +507,7 @@ SOURCES += \
TimeInZone.cpp \
TimeUtils.cpp \
ToolsDialog.cpp \
ToolsRhoEstimator.cpp \
TrainTool.cpp \
TreeMapWindow.cpp \
TreeMapPlot.cpp \