USGS

Isis 3.0 Object Programmers' Reference

Home

AtmosModel.cpp

00001 #include <cmath>
00002 #include <string>
00003 #include "Pvl.h"
00004 #include "iString.h"
00005 #include "AtmosModel.h"
00006 #include "NumericalApproximation.h"
00007 #include "NumericalAtmosApprox.h"
00008 #include "PhotoModel.h"
00009 #include "Minnaert.h"
00010 #include "LunarLambert.h"
00011 #include "Plugin.h"
00012 #include "iException.h"
00013 #include "Filename.h"
00014 
00015 using namespace std;
00016 
00017 #define MAX(x,y) (((x) > (y)) ? (x) : (y))
00018 namespace Isis {
00035   AtmosModel::AtmosModel (Pvl &pvl, PhotoModel &pmodel) {
00036     p_atmosAlgorithmName = "Unknown";
00037     p_atmosPM = &pmodel;
00038 
00039     p_atmosIncTable.resize(91);
00040     p_atmosAhTable.resize(91);
00041     p_atmosHahgtTable.resize(91);
00042     p_atmosHahgt0Table.resize(91);
00043     p_atmosAb = 0.0;
00044     p_atmosCosphi = 0.0;
00045     p_atmosAtmSwitch = 0;
00046     p_atmosEulgam = 0.5772156;
00047     p_atmosHahgsb = 0.0;
00048     p_atmosHga = 0.68;
00049     p_atmosInc = 0.0;
00050     p_atmosMunot = 0.0;
00051     p_atmosNinc = 91;
00052     p_atmosPhi = 0.0;
00053     p_atmosSini = 0.0;
00054     p_atmosTau = 0.28;
00055     p_atmosTauref = 0.0;
00056     p_atmosTauold = -1.0;
00057     p_atmosWha = 0.95;
00058     p_atmosWhaold = 1.0e30;
00059     p_atmosBha = 0.85;
00060     p_pstd = 0.0;
00061     p_sbar = 0.0;
00062     p_trans = 0.0;
00063     p_trans0 = 0.0;
00064     p_standardConditions = false;
00065 
00066     PvlGroup &algorithm = pvl.FindObject("AtmosphericModel").FindGroup("Algorithm",Pvl::Traverse);
00067 
00068     if (algorithm.HasKeyword("Nulneg")) {
00069       SetAtmosNulneg( ((string)algorithm["Nulneg"]).compare("YES") == 0 );
00070     }
00071     else {
00072       p_atmosNulneg = false;
00073     }
00074 
00075     if (algorithm.HasKeyword("Tau")) {
00076       SetAtmosTau( algorithm["Tau"] );
00077     }
00078     p_atmosTausave = p_atmosTau;
00079 
00080     if (algorithm.HasKeyword("Tauref")) {
00081       SetAtmosTauref( algorithm["Tauref"] );
00082     }
00083 
00084     if (algorithm.HasKeyword("Wha")) {
00085       SetAtmosWha( algorithm["Wha"] );
00086     }
00087     p_atmosWhasave = p_atmosWha;
00088 
00089     if (algorithm.HasKeyword("Wharef")) {
00090       SetAtmosWharef( algorithm["Wharef"] );
00091     } else {
00092       p_atmosWharef = p_atmosWha;
00093     }
00094 
00095     if (algorithm.HasKeyword("Hga")) {
00096       SetAtmosHga( algorithm["Hga"] );
00097     }
00098     p_atmosHgasave = p_atmosHga;
00099 
00100     if (algorithm.HasKeyword("Hgaref")) {
00101       SetAtmosHgaref( algorithm["Hgaref"] );
00102     } else {
00103       p_atmosHgaref = p_atmosHga;
00104     }
00105 
00106     if (algorithm.HasKeyword("Bha")) {
00107       SetAtmosBha( algorithm["Bha"] );
00108     }
00109     p_atmosBhasave = p_atmosBha;
00110 
00111     if (algorithm.HasKeyword("Bharef")) {
00112       SetAtmosBharef( algorithm["Bharef"] );
00113     } else {
00114       p_atmosBharef = p_atmosBha;
00115     }
00116 
00117     if (algorithm.HasKeyword("Inc")) {
00118       SetAtmosInc( algorithm["Inc"] );
00119     }
00120 
00121     if (algorithm.HasKeyword("Phi")) {
00122       SetAtmosPhi( algorithm["Phi"] );
00123     }
00124   }
00125 
00126 
00146   double AtmosModel::G11Prime(double tau) {
00147     double sum;
00148     int icnt;
00149     double fac;
00150     double term;
00151     double tol;
00152     double fi;
00153     double elog;
00154     double eulgam;
00155     double e1_2;
00156     double result;
00157 
00158     tol = 1.0e-6;
00159     eulgam = 0.5772156;
00160 
00161     sum = 0.0;
00162     icnt = 1;
00163     fac = -tau;
00164     term = fac;
00165     while (fabs(term) > fabs(sum)*tol) {
00166       sum = sum + term;
00167       icnt = icnt + 1;
00168       fi = (double) icnt;
00169       fac = fac * (-tau) / fi;
00170       term = fac / (fi * fi);
00171     }
00172     elog = log(MAX(1.0e-30,tau)) + eulgam;
00173     e1_2 = sum + PI * PI / 12.0 + 0.5 *
00174         pow(elog,2.0);
00175     result = 2.0 * (AtmosModel::En(1,tau) 
00176                     + elog * AtmosModel::En(2,tau) 
00177                     - tau * e1_2);
00178     return(result);
00179   } 
00180 
00222   double AtmosModel::Ei (double x) throw (iException &){
00223     //static double r8ei(double x) in original NumericalMethods class
00224     // This method was derived from an algorithm in the text 
00225     // Numerical Recipes in C: The Art of Scientific Computing
00226     // Section 6.3 by Flannery, Press, Teukolsky, and Vetterling
00227     double result;
00228     double fpmin; // close to smallest representable floating-point number
00229     double euler; // Euler's constant, lower-case gamma
00230     double epsilon;   // desired relative error (tolerance)
00231     int maxit;    // maximum number of iterations allowed
00232     double sum,fact,term,prev;
00233 
00234     fpmin = 1.0e-30;
00235     maxit = 100;
00236     epsilon = 6.0e-8;
00237     euler = 0.57721566;
00238 
00239     if (x <= 0.0) {
00240       throw iException::Message(iException::Programmer,
00241                                 "AtmosModel::Ei() - Invalid argument. Definition requires x > 0.0. Entered x = "
00242                                 + iString(x),
00243                                 _FILEINFO_);
00244     }
00245     if (x < fpmin) { //special case: avoid failure of convergence test because underflow
00246       result = log(x) + euler;
00247     } 
00248     else if (x <= -log(epsilon)) { //Use power series
00249       sum = 0.0;
00250       fact = 1.0;
00251       for (int k=1; k<=maxit; k++) {
00252         fact = fact * x / k;
00253         term = fact / k;
00254         sum = sum + term;
00255         if (term < epsilon*sum) {
00256           result = sum + log(x) + euler;
00257           return(result);
00258         }
00259       }
00260       throw iException::Message(iException::Math,
00261                                 "AtmosModel::Ei() - Power series failed to converge in " 
00262                                 + iString(maxit) + " iterations. Unable to calculate exponential integral.",
00263                                 _FILEINFO_);
00264     } 
00265     else { // Use asymptotic series
00266       sum = 0.0;
00267       term = 1.0;
00268       for (int k=1; k<=maxit; k++) {
00269         prev = term;
00270         term = term * k / x;
00271         if (term < epsilon) {
00272           result = exp(x) * (1.0 + sum) / x;
00273           return(result);
00274         } 
00275         else {
00276           if (term < prev) { // still converging: add new term
00277             sum = sum + term;
00278           } 
00279           else { // diverging: subtract previous term and exit
00280             sum = sum - prev;
00281             result = exp(x) * (1.0 + sum) / x;
00282             return(result);
00283           }
00284         }
00285       }
00286       result = exp(x) * (1.0 + sum) / x;
00287     }
00288     return(result);
00289   }
00290 
00363   double AtmosModel::En (unsigned int n, double x) throw (iException &){
00364     //static double r8expint(int n, double x) in original NumericalMethods class
00365     // This method was derived from an algorithm in the text 
00366     // Numerical Recipes in C: The Art of Scientific Computing
00367     // Section 6.3 by Flannery, Press, Teukolsky, and Vetterling
00368     int nm1;
00369     double result;
00370     double a,b,c,d,h;
00371     double delta;
00372     double fact;
00373     double psi;
00374     double fpmin;  // close to smallest representable floating-point number 
00375     double maxit;  // maximum number of iterations allowed                  
00376     double epsilon;    // desired relative error (tolerance)                    
00377     double euler;  // Euler's constant, gamma                               
00378 
00379     fpmin = 1.0e-30;
00380     maxit = 100;
00381     epsilon = 1.0e-7;
00382     euler = 0.5772156649;
00383 
00384     nm1 = n - 1;
00385     if (n < 0 || x < 0.0 || (x == 0.0 && (n == 0 || n == 1))) {
00386       iString msg = "AtmosModel::En() - Invalid arguments. ";
00387         msg+= "Definition requires (x > 0.0 and n >=0 ) or (x >= 0.0 and n > 1). ";
00388         msg+= "Entered x = " + iString(x) + " and n = " + iString((int) n);
00389       throw iException::Message(iException::Programmer,msg,
00390                                 _FILEINFO_);
00391     } 
00392     else if (n == 0) { // special case, this implies x > 0 by logic above
00393       result = exp(-x) / x;
00394     } 
00395     else if (x == 0.0) { // special case, this implies n > 1
00396       result = 1.0 / nm1;
00397     } 
00398     else if (x > 1.0) { // Lentz's algorithm, this implies n > 0
00399       b = x + n;
00400       c = 1.0 / fpmin;
00401       d = 1.0 / b;
00402       h = d;
00403       for (int i=1; i<=maxit; i++) {
00404         a = -i * (nm1 + i);
00405         b = b + 2.0;
00406         d = 1.0 / (a * d + b);
00407         c = b + a / c;
00408         delta = c * d;
00409         h = h * delta;
00410         if (fabs(delta-1.0) < epsilon) {
00411           result = h * exp(-x);
00412           return(result);
00413         }
00414       }
00415       throw iException::Message(iException::Math,
00416                                 "AtmosModel::En() - Continued fraction failed to converge in "
00417                                 + iString(maxit) + " iterations. Unable to calculate exponential integral.", 
00418                                 _FILEINFO_);
00419     } 
00420     else { // evaluate series
00421       if (nm1 != 0) {
00422         result = 1.0 / nm1;
00423       } 
00424       else {
00425         result = -log(x) - euler;
00426       }
00427       fact = 1.0;
00428       for (int i=1; i<=maxit; i++) {
00429         fact = -fact * x / i;
00430         if (i != nm1) {
00431           delta = -fact / (i - nm1);
00432         } 
00433         else {
00434           psi = -euler;
00435           for (int ii=1; ii<=nm1; ii++) {
00436             psi = psi + 1.0 / ii;
00437           }
00438           delta = fact * (-log(x) + psi);
00439         }
00440         result = result + delta;
00441         if (fabs(delta) < fabs(result)*epsilon) {
00442           return(result);
00443         }
00444       }
00445       throw iException::Message(iException::Math,
00446                                 "AtmosModel::En() - Series representation failed to converge in "
00447                                 + iString(maxit) + " iterations. Unable to calculate exponential integral.",
00448                                 _FILEINFO_);
00449     }
00450     return(result);
00451   }
00452 
00469   void AtmosModel::CalcAtmEffect(double pha, double inc, double ema, 
00470         double *pstd, double *trans, double *trans0, double *sbar) {
00471 
00472     // Check validity of photometric angles
00473     //if (pha > 180.0 || inc > 90.0 || ema > 90.0 || pha < 0.0 ||
00474     //    inc < 0.0 || ema < 0.0) {
00475     //  string msg = "Invalid photometric angles";
00476     //  throw iException::Message(iException::Programmer,msg,_FILEINFO_);
00477     //}
00478 
00479     // Apply atmospheric function
00480     AtmosModelAlgorithm(pha,inc,ema);
00481     *pstd = p_pstd;
00482     *trans = p_trans;
00483     *trans0 = p_trans0;
00484     *sbar = p_sbar;
00485   }
00486 
00490   void AtmosModel::SetStandardConditions(bool standard) {
00491     p_standardConditions = standard;
00492     if (p_standardConditions) {
00493       p_atmosBhasave = p_atmosBha;
00494       p_atmosBha = p_atmosBharef;
00495       p_atmosHgasave = p_atmosHga;
00496       p_atmosHga = p_atmosHgaref;
00497       p_atmosTausave = p_atmosTau;
00498       p_atmosTau = p_atmosTauref;
00499       p_atmosWhasave = p_atmosWha;
00500       p_atmosWha = p_atmosWharef;
00501     } else {
00502       p_atmosBha = p_atmosBhasave;
00503       p_atmosHga = p_atmosHgasave;
00504       p_atmosTau = p_atmosTausave;
00505       p_atmosWha = p_atmosWhasave;
00506     }
00507   }
00508 
00537   void AtmosModel::GenerateAhTable() { 
00538     int inccnt;     //for loop incidence angle count, 1:ninc
00539     double fun_temp;//temporary variable for integral
00540     double factor;  //factor for integration: 1 except at ends of interval where it's 1/2
00541     double yp1,yp2; //first derivatives of first and last x values of @a p_atmosIncTable
00542 
00543     NumericalAtmosApprox::IntegFunc sub;
00544     sub = NumericalAtmosApprox::OuterFunction;
00545 
00546     p_atmosAb = 0.0;
00547 
00548     //Create NumericalAtmosApprox object here for RombergsMethod used in for loop
00549     NumericalAtmosApprox qromb;
00550 
00551     for (inccnt=0; inccnt<p_atmosNinc; inccnt++) {
00552       p_atmosInc = (double) inccnt;
00553       p_atmosIncTable[inccnt] = p_atmosInc;
00554       p_atmosMunot = cos((PI/180.0)*p_atmosInc);
00555       p_atmosSini = sin((PI/180.0)*p_atmosInc);
00556 
00557       iString phtName = p_atmosPM->AlgorithmName();
00558       phtName.UpCase();
00559       if (p_atmosInc == 90.0) {
00560         p_atmosAhTable[inccnt] = 0.0;
00561       }
00562       else if (phtName == "LAMBERT") {
00563         p_atmosAhTable[inccnt] = 1.0;
00564       }
00565       else if (phtName == "LOMMELSEELIGER") {
00566         p_atmosAhTable[inccnt] = 2.0 * log((1.0/p_atmosMunot)/p_atmosMunot);
00567       }
00568       else if (phtName == "MINNAERT") {
00569         p_atmosAhTable[inccnt] = (pow(p_atmosMunot,((Minnaert*)p_atmosPM)->PhotoK()))/((Minnaert*)p_atmosPM)->PhotoK();
00570       }
00571       else if (phtName == "LUNARLAMBERT") {
00572         p_atmosAhTable[inccnt] = 2.0 * ((LunarLambert*)p_atmosPM)->PhotoL() * 
00573                                  log((1.0+p_atmosMunot)/p_atmosMunot) + 1.0 - 
00574                                  ((LunarLambert*)p_atmosPM)->PhotoL();
00575       }
00576       else {
00577         // numerically integrate the other photometric models    
00578         // outer integral is over phi from 0 to pi rad = 180 deg 
00579         p_atmosAtmSwitch = 0;
00580         // integrate AtmosModel function from 0 to 180
00581         fun_temp = qromb.RombergsMethod(this,sub,0,180);
00582         // the correct normalization with phi in degrees is:
00583         p_atmosAhTable[inccnt] = fun_temp / (90.0*p_atmosMunot);
00584       }
00585       // Let's get our estimate of Ab by integrating (summing)
00586       // A (i)sinicosi over our A  table                      
00587       if ((inccnt == 0) || (inccnt == p_atmosNinc-1)) {
00588         factor = 0.5;
00589       }
00590       else {
00591         factor = 1.0;
00592       }
00593 
00594       p_atmosAb = p_atmosAb + p_atmosAhTable[inccnt] * p_atmosMunot * p_atmosSini * factor;
00595     }
00596 
00597     factor = 2.0 * PI/180.0;
00598     p_atmosAb = p_atmosAb * factor;
00599 
00600     // set up clamped cubic spline
00601     yp1 = 1.0e30;
00602     yp2 = 1.0e30;
00603     p_atmosAhSpline.Reset();
00604     p_atmosAhSpline.SetInterpType(NumericalApproximation::CubicClamped);
00605     p_atmosAhSpline.AddData(p_atmosIncTable,p_atmosAhTable);
00606     p_atmosAhSpline.SetCubicClampedEndptDeriv(yp1,yp2);
00607   }
00608 
00637   void AtmosModel::GenerateHahgTables() { 
00638     int inccnt;         // for loop incidence angle count,1:ninc
00639     double fun_temp;    // temporary variable for integral
00640     double hahgsb_temp; // save increment to hahgsb
00641     double factor;      // factor for integration: 1 except at ends of interval where it's 1/2
00642     double yp1,yp2;     // derivatives of endpoints of data set
00643 
00644     NumericalAtmosApprox::IntegFunc sub;
00645     sub = NumericalAtmosApprox::OuterFunction;
00646 
00647     p_atmosHahgsb = 0.0;
00648     NumericalAtmosApprox qromb;
00649 
00650     for (inccnt = 0; inccnt < p_atmosNinc; inccnt++) {
00651       p_atmosInc = (double) inccnt;
00652       p_atmosIncTable[inccnt] = p_atmosInc;
00653       p_atmosMunot = cos((PI/180.0)*p_atmosInc);
00654       p_atmosSini = sin((PI/180.0)*p_atmosInc);
00655 
00656       p_atmosAtmSwitch = 1;
00657 
00658       qromb.Reset();
00659       fun_temp = qromb.RombergsMethod(this,sub,0,180);
00660 
00661       p_atmosHahgtTable[inccnt] = fun_temp * AtmosWha() / 360.0;
00662       p_atmosAtmSwitch = 2;
00663 
00664       fun_temp = qromb.RombergsMethod(this,sub,0,180);
00665 
00666       hahgsb_temp = fun_temp * AtmosWha() / 360.0;
00667 
00668       if ((inccnt == 0) || (inccnt == p_atmosNinc-1)) {
00669         factor = 0.5;
00670       }
00671       else {
00672         factor = 1.0;
00673       }
00674 
00675       p_atmosHahgsb = p_atmosHahgsb + p_atmosSini * factor * hahgsb_temp;
00676       if (p_atmosInc == 0.0) {
00677         p_atmosHahgt0Table[inccnt] = 0.0;
00678       }
00679       else {
00680         p_atmosAtmSwitch = 3;
00681         fun_temp = qromb.RombergsMethod(this,sub,0,180);
00682         p_atmosHahgt0Table[inccnt] = fun_temp * AtmosWha() * p_atmosMunot / (360.0 * p_atmosSini);
00683       }
00684     }
00685 
00686     factor = 2.0 * PI/180.0;
00687     p_atmosHahgsb = p_atmosHahgsb * factor;
00688 
00689     // set up clamped cubic splines
00690     yp1 = 1.0e30;
00691     yp2 = 1.0e30;
00692     p_atmosHahgtSpline.Reset();
00693     p_atmosHahgtSpline.SetInterpType(NumericalApproximation::CubicClamped);
00694     p_atmosHahgtSpline.AddData(p_atmosIncTable,p_atmosHahgtTable); 
00695     p_atmosHahgtSpline.SetCubicClampedEndptDeriv(yp1,yp2);
00696 
00697     p_atmosHahgt0Spline.Reset();
00698     p_atmosHahgt0Spline.SetInterpType(NumericalApproximation::CubicClamped);
00699     p_atmosHahgt0Spline.AddData(p_atmosIncTable,p_atmosHahgt0Table);
00700     p_atmosHahgt0Spline.SetCubicClampedEndptDeriv(yp1,yp2);
00701   }
00702 
00714   void AtmosModel::SetAtmosAtmSwitch (const int atmswitch) {
00715     if (atmswitch < 0 || atmswitch > 3) {
00716       string msg = "Invalid value of atmospheric atmswitch [" + iString(atmswitch) + "]";
00717       throw iException::Message(iException::User,msg,_FILEINFO_);
00718     }
00719 
00720     p_atmosAtmSwitch = atmswitch;
00721   }
00722 
00734   void AtmosModel::SetAtmosBha (const double bha) {
00735     if (bha < -1.0 || bha > 1.0) {
00736       string msg = "Invalid value of Anisotropic atmospheric bha [" +
00737                         iString(bha) + "]";
00738       throw iException::Message(iException::User,msg,_FILEINFO_);
00739     }
00740 
00741     p_atmosBha = bha;
00742   }
00743 
00755   void AtmosModel::SetAtmosBharef (const double bharef) {
00756     if (bharef < -1.0 || bharef > 1.0) {
00757       string msg = "Invalid value of Atmospheric bharef [" + iString(bharef) + "]";
00758       throw iException::Message(iException::User,msg,_FILEINFO_);
00759     }
00760 
00761     p_atmosBharef = bharef;
00762   }
00763 
00775   void AtmosModel::SetAtmosHga (const double hga) {
00776     if (hga <= -1.0 || hga >= 1.0) {
00777       string msg = "Invalid value of Hapke atmospheric hga [" + iString(hga) + "]";
00778       throw iException::Message(iException::User,msg,_FILEINFO_);
00779     }
00780 
00781     p_atmosHga = hga;
00782   }
00783 
00795   void AtmosModel::SetAtmosHgaref (const double hgaref) {
00796     if (hgaref <= -1.0 || hgaref >= 1.0) {
00797       string msg = "Invalid value of Atmospheric hgaref [" + iString(hgaref) + "]";
00798       throw iException::Message(iException::User,msg,_FILEINFO_);
00799     }
00800 
00801     p_atmosHgaref = hgaref;
00802   }
00803 
00814   void AtmosModel::SetAtmosInc (const double inc) {
00815     if (inc < 0.0 || inc > 90.0) {
00816       string msg = "Invalid value of atmospheric inc [" + iString(inc) + "]";
00817       throw iException::Message(iException::User,msg,_FILEINFO_);
00818     }
00819 
00820     p_atmosInc = inc;
00821     p_atmosMunot = cos(inc*PI/180.0);
00822     p_atmosSini = sin(inc*PI/180.0);
00823   }
00824 
00836   void AtmosModel::SetAtmosNulneg (const string nulneg) {
00837     iString temp(nulneg);
00838     temp = temp.UpCase();
00839 
00840     if (temp != "NO" && temp != "YES") {
00841       string msg = "Invalid value of Atmospheric nulneg [" + temp + "]";
00842       throw iException::Message(iException::User,msg,_FILEINFO_);
00843     }
00844 
00845     SetAtmosNulneg( temp.compare("YES") == 0 );
00846   }
00847 
00859   void AtmosModel::SetAtmosPhi (const double phi) {
00860     if (phi < 0.0 || phi > 360.0) {
00861       string msg = "Invalid value of atmospheric phi [" + iString(phi) + "]";
00862       throw iException::Message(iException::User,msg,_FILEINFO_);
00863     }
00864 
00865     p_atmosPhi = phi;
00866     p_atmosCosphi = cos(phi*PI/180.0);
00867   }
00868 
00878   void AtmosModel::SetAtmosTau (const double tau) {
00879     if (tau < 0.0) {
00880       string msg = "Invalid value of Atmospheric tau [" + iString(tau) + "]";
00881       throw iException::Message(iException::User,msg,_FILEINFO_);
00882     }
00883 
00884     p_atmosTau = tau;
00885   }
00886 
00897   void AtmosModel::SetAtmosTauref (const double tauref) {
00898     if (tauref < 0.0) {
00899       string msg = "Invalid value of Atmospheric tauref [" + iString(tauref) + "]";
00900       throw iException::Message(iException::User,msg,_FILEINFO_);
00901     }
00902 
00903     p_atmosTauref = tauref;
00904   }
00905 
00916   void AtmosModel::SetAtmosWha (const double wha) {
00917     if (wha <= 0.0 || wha > 1.0) {
00918       string msg = "Invalid value of Atmospheric wha [" + iString(wha) + "]";
00919       throw iException::Message(iException::User,msg,_FILEINFO_);
00920     }
00921 
00922     p_atmosWha = wha;
00923   }
00924 
00935   void AtmosModel::SetAtmosWharef (const double wharef) {
00936     if (wharef <= 0.0 || wharef > 1.0) {
00937       string msg = "Invalid value of Atmospheric wharef [" + iString(wharef) + "]";
00938       throw iException::Message(iException::User,msg,_FILEINFO_);
00939     }
00940 
00941     p_atmosWharef = wharef;
00942   }
00943 
00948   bool AtmosModel::TauOrWhaChanged() const {
00949     return (AtmosTau() != p_atmosTauold) || (AtmosWha() != p_atmosWhaold);
00950   }
00951 }