first commit
This commit is contained in:
43
src/Environment/CMakeLists.txt
Normal file
43
src/Environment/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
include(FlightGearComponent)
|
||||
|
||||
set(SOURCES
|
||||
atmosphere.cxx
|
||||
environment.cxx
|
||||
environment_ctrl.cxx
|
||||
environment_mgr.cxx
|
||||
ephemeris.cxx
|
||||
climate.cxx
|
||||
fgclouds.cxx
|
||||
fgmetar.cxx
|
||||
metarairportfilter.cxx
|
||||
metarproperties.cxx
|
||||
precipitation_mgr.cxx
|
||||
realwx_ctrl.cxx
|
||||
ridge_lift.cxx
|
||||
terrainsampler.cxx
|
||||
presets.cxx
|
||||
gravity.cxx
|
||||
magvarmanager.cxx
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
atmosphere.hxx
|
||||
environment.hxx
|
||||
environment_ctrl.hxx
|
||||
environment_mgr.hxx
|
||||
ephemeris.hxx
|
||||
fgclouds.hxx
|
||||
climate.hxx
|
||||
fgmetar.hxx
|
||||
metarairportfilter.hxx
|
||||
metarproperties.hxx
|
||||
precipitation_mgr.hxx
|
||||
realwx_ctrl.hxx
|
||||
ridge_lift.hxx
|
||||
terrainsampler.hxx
|
||||
presets.hxx
|
||||
gravity.hxx
|
||||
magvarmanager.hxx
|
||||
)
|
||||
|
||||
flightgear_component(Environment "${SOURCES}" "${HEADERS}")
|
||||
319
src/Environment/atmosphere.cxx
Normal file
319
src/Environment/atmosphere.cxx
Normal file
@@ -0,0 +1,319 @@
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include "atmosphere.hxx"
|
||||
|
||||
using namespace std;
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
const ISA_layer ISA_def[] = {
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// id (m) (ft) (Pa) (inHg) (K) (C) (K/m) (K/ft)
|
||||
ISA_layer(0, 0, 0, 101325, 29.92126, 288.15, 15.00, 0.0065, 0.0019812),
|
||||
ISA_layer(1, 11000, 36089, 22632.1, 6.683246, 216.65, -56.50, 0, 0),
|
||||
ISA_layer(2, 20000, 65616, 5474.89, 1.616734, 216.65, -56.50, -0.0010, -0.0003048),
|
||||
ISA_layer(3, 32000, 104986, 868.019, 0.256326, 228.65, -44.50, -0.0028, -0.0008534),
|
||||
ISA_layer(4, 47000, 154199, 110.906, 0.0327506, 270.65, -2.50, 0, 0),
|
||||
ISA_layer(5, 51000, 167322, 66.9389, 0.0197670, 270.65, -2.50, 0.0028, 0.0008534),
|
||||
ISA_layer(6, 71000, 232939, 3.95642, 0.00116833, 214.65, -58.50, 0.0020, 0.0006096),
|
||||
ISA_layer(7, 80000, 262467, 0.88628, 0.000261718, 196.65, -76.50, 0.0, 0.0),
|
||||
// The last layer MUST have -1.0 for its 'lapse' field
|
||||
ISA_layer(8, 1.0e9, 3.28e9, 0.00001, 3.0e-9, 2.73, -270.4, -1.0)
|
||||
};
|
||||
|
||||
// Pressure within a layer, as a function of height.
|
||||
// Physics model: standard or nonstandard atmosphere,
|
||||
// depending on what parameters you pass in.
|
||||
// Height in meters, pressures in pascals.
|
||||
// As always, lapse is positive in the troposphere,
|
||||
// and zero in the first part of the stratosphere.
|
||||
|
||||
double P_layer(const double height, const double href,
|
||||
const double Pref, const double Tref,
|
||||
const double lapse) {
|
||||
using namespace atmodel;
|
||||
if (lapse) {
|
||||
double N = lapse * Rgas / mm / g;
|
||||
return Pref * pow( (Tref - lapse*(height - href)) / Tref , (1/N));
|
||||
} else {
|
||||
return Pref * exp(-g * mm / Rgas / Tref * (height - href));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Temperature within a layer, as a function of height.
|
||||
// Physics model: standard or nonstandard atmosphere
|
||||
// depending on what parameters you pass in.
|
||||
// $hh in meters, pressures in Pa.
|
||||
// As always, $lambda is positive in the troposphere,
|
||||
// and zero in the first part of the stratosphere.
|
||||
double T_layer (
|
||||
const double hh,
|
||||
const double hb,
|
||||
const double Pb,
|
||||
const double Tb,
|
||||
const double lambda) {
|
||||
return Tb - lambda*(hh - hb);
|
||||
}
|
||||
|
||||
// Pressure and temperature as a function of height, Psl, and Tsl.
|
||||
// heights in meters, pressures in Pa.
|
||||
// Daisy chain version.
|
||||
// We need "seed" values for sea-level pressure and temperature.
|
||||
// In addition, for every layer, we need three things
|
||||
// from the table: the reference height in that layer,
|
||||
// the lapse in that layer, and the cap (if any) for that layer
|
||||
// (which we take from the /next/ row of the table, if any).
|
||||
pair<double,double> PT_vs_hpt(
|
||||
const double hh,
|
||||
const double _p0,
|
||||
const double _t0
|
||||
) {
|
||||
|
||||
const double d0(0);
|
||||
double hgt = ISA_def[0].height;
|
||||
double p0 = _p0;
|
||||
double t0 = _t0;
|
||||
#if 0
|
||||
cout << "PT_vs_hpt: " << hh << " " << p0 << " " << t0 << endl;
|
||||
#endif
|
||||
|
||||
int ii = 0;
|
||||
for (const ISA_layer* pp = ISA_def; pp->lapse != -1; pp++, ii++) {
|
||||
#if 0
|
||||
cout << "PT_vs_hpt: " << ii
|
||||
<< " height: " << pp->height
|
||||
<< " temp: " << pp->temp
|
||||
<< " lapse: " << pp->lapse
|
||||
<< endl;
|
||||
#endif
|
||||
double xhgt(9e99);
|
||||
double lapse = pp->lapse;
|
||||
// Stratosphere starts at a definite temperature,
|
||||
// not a definite height:
|
||||
if (ii == 0) {
|
||||
xhgt = hgt + (t0 - (pp+1)->temp) / lapse;
|
||||
} else if ((pp+1)->lapse != -1) {
|
||||
xhgt = (pp+1)->height;
|
||||
}
|
||||
if (hh <= xhgt) {
|
||||
return make_pair(P_layer(hh, hgt, p0, t0, lapse),
|
||||
T_layer(hh, hgt, p0, t0, lapse));
|
||||
}
|
||||
p0 = P_layer(xhgt, hgt, p0, t0, lapse);
|
||||
t0 = t0 - lapse * (xhgt - hgt);
|
||||
hgt = xhgt;
|
||||
}
|
||||
|
||||
// Should never get here.
|
||||
SG_LOG(SG_ENVIRONMENT, SG_ALERT, "PT_vs_hpt: ran out of layers for h=" << hh );
|
||||
return make_pair(d0, d0);
|
||||
}
|
||||
|
||||
|
||||
FGAtmoCache::FGAtmoCache() :
|
||||
a_tvs_p(0)
|
||||
{}
|
||||
|
||||
FGAtmoCache::~FGAtmoCache() {
|
||||
delete a_tvs_p;
|
||||
}
|
||||
|
||||
|
||||
/////////////
|
||||
// The following two routines are called "fake" because they
|
||||
// bypass the exceedingly complicated layer model implied by
|
||||
// the "weather conditioins" popup menu.
|
||||
// For now we must bypass it for several reasons, including
|
||||
// the fact that we don't have an "environment" object for
|
||||
// the airport (only for the airplane).
|
||||
// degrees C, height in feet
|
||||
double FGAtmo::fake_T_vs_a_us(const double h_ft,
|
||||
const double Tsl) const {
|
||||
using namespace atmodel;
|
||||
return Tsl - ISA::lam0 * h_ft * foot;
|
||||
}
|
||||
|
||||
// Dewpoint. degrees C or K, height in feet
|
||||
double FGAtmo::fake_dp_vs_a_us(const double dpsl, const double h_ft) {
|
||||
const double dp_lapse(0.002); // [K/m] approximate
|
||||
// Reference: http://en.wikipedia.org/wiki/Lapse_rate
|
||||
return dpsl - dp_lapse * h_ft * atmodel::foot;
|
||||
}
|
||||
|
||||
// Height as a function of pressure.
|
||||
// Valid in the troposphere only.
|
||||
double FGAtmo::a_vs_p(const double press, const double qnh) {
|
||||
using namespace atmodel;
|
||||
using namespace ISA;
|
||||
double nn = lam0 * Rgas / g / mm;
|
||||
return T0 * ( pow(qnh/P0,nn) - pow(press/P0,nn) ) / lam0;
|
||||
}
|
||||
|
||||
// force retabulation
|
||||
void FGAtmoCache::tabulate() {
|
||||
using namespace atmodel;
|
||||
delete a_tvs_p;
|
||||
a_tvs_p = new SGInterpTable;
|
||||
|
||||
for (double hgt = -1000; hgt <= 32000;) {
|
||||
double press,temp;
|
||||
std::tie(press, temp) = PT_vs_hpt(hgt);
|
||||
a_tvs_p->addEntry(press / inHg, hgt / foot);
|
||||
|
||||
#ifdef DEBUG_EXPORT_P_H
|
||||
char buf[100];
|
||||
char* fmt = " { %9.2f , %5.0f },";
|
||||
if (press < 10000) fmt = " { %9.3f , %5.0f },";
|
||||
snprintf(buf, 100, fmt, press, hgt);
|
||||
cout << buf << endl;
|
||||
#endif
|
||||
if (hgt < 6000) {
|
||||
hgt += 500;
|
||||
} else {
|
||||
hgt += 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure cache is valid
|
||||
void FGAtmoCache::cache() {
|
||||
if (!a_tvs_p)
|
||||
tabulate();
|
||||
}
|
||||
|
||||
// Check the basic function,
|
||||
// then compare against the interpolator.
|
||||
void FGAtmoCache::check_model() {
|
||||
double hgts[] = {
|
||||
-1000,
|
||||
-250,
|
||||
0,
|
||||
250,
|
||||
1000,
|
||||
5250,
|
||||
11000,
|
||||
11000.00001,
|
||||
15500,
|
||||
20000,
|
||||
20000.00001,
|
||||
25500,
|
||||
32000,
|
||||
32000.00001,
|
||||
-9e99
|
||||
};
|
||||
|
||||
for (int i = 0; ; i++) {
|
||||
double height = hgts[i];
|
||||
if (height < -1e6)
|
||||
break;
|
||||
using namespace atmodel;
|
||||
cache();
|
||||
double press,temp;
|
||||
std::tie(press, temp) = PT_vs_hpt(height);
|
||||
cout << "Height: " << height
|
||||
<< " \tpressure: " << press << endl;
|
||||
cout << "Check: "
|
||||
<< a_tvs_p->interpolate(press / inHg)*foot << endl;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
FGAltimeter::FGAltimeter()
|
||||
{
|
||||
cache();
|
||||
}
|
||||
|
||||
double FGAltimeter::reading_ft(const double p_inHg, const double set_inHg) {
|
||||
using namespace atmodel;
|
||||
double press_alt = a_tvs_p->interpolate(p_inHg);
|
||||
double kollsman_shift = a_tvs_p->interpolate(set_inHg);
|
||||
return (press_alt - kollsman_shift);
|
||||
}
|
||||
|
||||
// Altimeter setting _in pascals_
|
||||
// ... caller gets to convert to inHg or millibars
|
||||
// Field elevation in m
|
||||
// Field pressure in pascals
|
||||
// Valid for fields within the troposphere only.
|
||||
double FGAtmo::QNH(const double field_elev, const double field_press) {
|
||||
using namespace atmodel;
|
||||
|
||||
// Equation derived in altimetry.htm
|
||||
// exponent in QNH equation:
|
||||
double nn = ISA::lam0 * Rgas / g / mm;
|
||||
// pressure ratio factor:
|
||||
double prat = pow(ISA::P0 / field_press, nn);
|
||||
double rslt = field_press
|
||||
* pow(1. + ISA::lam0 * field_elev / ISA::T0 * prat, 1./nn);
|
||||
#if 0
|
||||
SG_LOG(SG_ENVIRONMENT, SG_ALERT, "QNH: elev: " << field_elev
|
||||
<< " press: " << field_press
|
||||
<< " prat: " << prat
|
||||
<< " rslt: " << rslt
|
||||
<< " inHg: " << inHg
|
||||
<< " rslt/inHG: " << rslt/inHg);
|
||||
#endif
|
||||
return rslt;
|
||||
}
|
||||
|
||||
// Invert the QNH calculation to get the field pressure from a metar
|
||||
// report.
|
||||
// field pressure _in pascals_
|
||||
// ... caller gets to convert to inHg or millibars
|
||||
// Field elevation in m
|
||||
// Altimeter setting (QNH) in pascals
|
||||
// Valid for fields within the troposphere only.
|
||||
double FGAtmo::fieldPressure(const double field_elev, const double qnh)
|
||||
{
|
||||
using namespace atmodel;
|
||||
static const double nn = ISA::lam0 * Rgas / g / mm;
|
||||
const double pratio = pow(qnh / ISA::P0, nn);
|
||||
return ISA::P0 * pow(pratio - field_elev * ISA::lam0 / ISA::T0, 1.0 / nn);
|
||||
}
|
||||
|
||||
void FGAltimeter::dump_stack1(const double Tref) {
|
||||
using namespace atmodel;
|
||||
const int bs(200);
|
||||
char buf[bs];
|
||||
double Psl = P_layer(0, 0, ISA::P0, Tref, ISA::lam0);
|
||||
snprintf(buf, bs, "Tref: %6.2f Psl: %5.0f = %7.4f",
|
||||
Tref, Psl, Psl / inHg);
|
||||
cout << buf << endl;
|
||||
|
||||
snprintf(buf, bs,
|
||||
" %6s %6s %6s %6s %6s %6s %6s",
|
||||
"A", "Aind", "Apr", "Aprind", "P", "Psl", "Qnh");
|
||||
cout << buf << endl;
|
||||
|
||||
double hgts[] = {0, 2500, 5000, 7500, 10000, -9e99};
|
||||
for (int ii = 0; ; ii++) {
|
||||
double hgt_ft = hgts[ii];
|
||||
double hgt = hgt_ft * foot;
|
||||
if (hgt_ft < -1e6)
|
||||
break;
|
||||
double press = P_layer(hgt, 0, ISA::P0, Tref, ISA::lam0);
|
||||
double qnhx = QNH(hgt, press) / inHg;
|
||||
double qnh2 = SGMiscd::round(qnhx*100)/100;
|
||||
|
||||
double p_inHg = press / inHg;
|
||||
double Aprind = reading_ft(p_inHg);
|
||||
double Apr = a_vs_p(p_inHg*inHg) / foot;
|
||||
double hind = reading_ft(p_inHg, qnh2);
|
||||
snprintf(buf, bs,
|
||||
" %6.0f %6.0f %6.0f %6.0f %6.2f %6.2f %6.2f",
|
||||
hgt_ft, hind, Apr, Aprind, p_inHg, Psl/inHg, qnh2);
|
||||
cout << buf << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FGAltimeter::dump_stack() {
|
||||
using namespace atmodel;
|
||||
cout << "........." << endl;
|
||||
cout << "Size: " << sizeof(FGAtmo) << endl;
|
||||
dump_stack1(ISA::T0);
|
||||
dump_stack1(ISA::T0 - 20);
|
||||
}
|
||||
155
src/Environment/atmosphere.hxx
Normal file
155
src/Environment/atmosphere.hxx
Normal file
@@ -0,0 +1,155 @@
|
||||
// atmosphere.hxx -- routines to model the air column
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
// Modified by John Denker to correct physics errors in 2007
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
||||
#ifndef _ATMOSPHERE_HXX
|
||||
#define _ATMOSPHERE_HXX
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/math/interpolater.hxx>
|
||||
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Model the atmosphere in a way consistent with the laws
|
||||
* of physics.
|
||||
*
|
||||
* Each instance of this class models a particular air mass.
|
||||
* You may freely move up, down, or sideways in the air mass.
|
||||
* In contrast, if you want to compare different air masses,
|
||||
* you should use a separate instance for each one.
|
||||
*
|
||||
* See also ./environment.hxx
|
||||
*/
|
||||
|
||||
#define SCD(name,val) const double name(val)
|
||||
namespace atmodel {
|
||||
SCD(g, 9.80665); // [m/s/s] acceleration of gravity
|
||||
SCD(mm, .0289644); // [kg/mole] molar mass of air (dry?)
|
||||
SCD(Rgas, 8.31432); // [J/K/mole] gas constant
|
||||
SCD(inch, 0.0254); // [m] definition of inch
|
||||
SCD(foot, 12 * inch); // [m]
|
||||
SCD(inHg, 101325.0 / 760 * 1000 * inch); // [Pa] definition of inHg
|
||||
SCD(mbar, 100.); // [Pa] definition of millibar
|
||||
SCD(freezing, 273.15); // [K] centigrade - kelvin offset
|
||||
SCD(nm, 1852); // [m] nautical mile (NIST)
|
||||
SCD(sm, 5280*foot); // [m] nautical mile (NIST)
|
||||
|
||||
namespace ISA {
|
||||
SCD(P0, 101325.0); // [pascals] ISA sea-level pressure
|
||||
SCD(T0, 15. + freezing); // [K] ISA sea-level temperature
|
||||
SCD(lam0, .0065); // [K/m] ISA troposphere lapse rate
|
||||
}
|
||||
}
|
||||
#undef SCD
|
||||
|
||||
|
||||
|
||||
class ISA_layer {
|
||||
public:
|
||||
double height;
|
||||
double temp;
|
||||
double lapse;
|
||||
ISA_layer(int, double h, double, double, double, double t, double,
|
||||
double l=-1, double=0)
|
||||
: height(h), // [meters]
|
||||
temp(t), // [kelvin]
|
||||
lapse(l) // [K/m]
|
||||
{}
|
||||
};
|
||||
|
||||
extern const ISA_layer ISA_def[];
|
||||
|
||||
std::pair<double,double> PT_vs_hpt(
|
||||
const double hh,
|
||||
const double _p0 = atmodel::ISA::P0,
|
||||
const double _t0 = atmodel::ISA::T0);
|
||||
|
||||
double P_layer(const double height, const double href,
|
||||
const double Pref, const double Tref, const double lapse );
|
||||
|
||||
double T_layer(const double height, const double href,
|
||||
const double Pref, const double Tref, const double lapse );
|
||||
|
||||
// The base class is little more than a namespace.
|
||||
// It has no constructor, no destructor, and no variables.
|
||||
class FGAtmo {
|
||||
public:
|
||||
double a_vs_p(const double press, const double qnh = atmodel::ISA::P0);
|
||||
double fake_T_vs_a_us(const double h_ft,
|
||||
const double Tsl = atmodel::ISA::T0) const;
|
||||
double fake_dp_vs_a_us(const double dpsl, const double h_ft);
|
||||
void check_one(const double height);
|
||||
|
||||
// Altimeter setting _in pascals_
|
||||
// ... caller gets to convert to inHg or millibars
|
||||
// Field elevation in m
|
||||
// Field pressure in pascals
|
||||
// Valid for fields within the troposphere only.
|
||||
double QNH(const double field_elev, const double field_press);
|
||||
/**
|
||||
* Invert the QNH calculation to get the field pressure from a metar
|
||||
* report. Valid for fields within the troposphere only.
|
||||
* @param field_elev field elevation in m
|
||||
* @param qnh altimeter setting in pascals
|
||||
* @return field pressure _in pascals_. Caller gets to convert to inHg
|
||||
* or millibars
|
||||
*/
|
||||
static double fieldPressure(const double field_elev, const double qnh);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FGAtmoCache : FGAtmo {
|
||||
friend class FGAltimeter;
|
||||
SGInterpTable * a_tvs_p; // _tvs_ means "tabulated versus"
|
||||
|
||||
public:
|
||||
FGAtmoCache();
|
||||
~FGAtmoCache();
|
||||
void tabulate();
|
||||
void cache();
|
||||
void check_model(); // debug
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FGAltimeter : public FGAtmoCache {
|
||||
public:
|
||||
FGAltimeter();
|
||||
double reading_ft(const double p_inHg,
|
||||
const double set_inHg = atmodel::ISA::P0/atmodel::inHg);
|
||||
inline double press_alt_ft(const double p_inHg) {
|
||||
return a_tvs_p->interpolate(p_inHg);
|
||||
}
|
||||
inline double kollsman_ft(const double set_inHg) {
|
||||
return a_tvs_p->interpolate(set_inHg);
|
||||
}
|
||||
|
||||
// debug
|
||||
void dump_stack();
|
||||
void dump_stack1(const double Tref);
|
||||
};
|
||||
|
||||
#endif // _ATMOSPHERE_HXX
|
||||
1270
src/Environment/climate.cxx
Normal file
1270
src/Environment/climate.cxx
Normal file
File diff suppressed because it is too large
Load Diff
197
src/Environment/climate.hxx
Normal file
197
src/Environment/climate.hxx
Normal file
@@ -0,0 +1,197 @@
|
||||
// Köppen-Geiger climate interface class
|
||||
//
|
||||
// Written by Erik Hofman, started October 2020
|
||||
//
|
||||
// Copyright (C) 2020 by Erik Hofman <erik@ehofman.com>
|
||||
//
|
||||
// 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.,
|
||||
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef _FGCLIMATE_HXX
|
||||
#define _FGCLIMATE_HXX
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Image>
|
||||
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
#include <simgear/math/SGGeod.hxx>
|
||||
|
||||
#define REPORT_TO_CONSOLE 0
|
||||
|
||||
/*
|
||||
* Update environment parameters based on the Köppen-Geiger climate
|
||||
* map of the world based on lattitude and longitude.
|
||||
*/
|
||||
|
||||
class FGLight;
|
||||
|
||||
#define MAX_CLIMATE_CLASSES 32
|
||||
|
||||
class FGClimate : public SGSubsystem {
|
||||
private:
|
||||
struct _ground_tile {
|
||||
SGGeod pos;
|
||||
|
||||
double elevation_m = 0.0;
|
||||
double temperature = -99999.0;
|
||||
double temperature_mean = -99999.0;
|
||||
double temperature_water = -99999.0;
|
||||
double relative_humidity = -99999.0;
|
||||
double precipitation_annual = -99999.0;
|
||||
double precipitation = -99999.0;
|
||||
bool has_autumn = false;
|
||||
|
||||
double dewpoint = -99999.0;
|
||||
double pressure = 0.0;
|
||||
|
||||
} _ground_tile;
|
||||
using ClimateTile = struct _ground_tile;
|
||||
|
||||
public:
|
||||
FGClimate();
|
||||
virtual ~FGClimate() = default;
|
||||
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;;
|
||||
|
||||
double get_snow_level_m() { return _snow_level; }
|
||||
double get_snow_thickness() { return _snow_thickness; }
|
||||
double get_ice_cover() { return _ice_cover; }
|
||||
double get_dust_cover() { return _dust_cover; }
|
||||
double get_wetness() { return _wetness; }
|
||||
double get_lichen_cover() { return _lichen_cover; }
|
||||
|
||||
double get_relative_humidity_pct() { return _gl.relative_humidity; }
|
||||
double get_relative_humidity_sea_level_pct() { return _sl.relative_humidity; }
|
||||
double get_pressure_hpa() { return _gl.pressure; }
|
||||
double get_pressure_sea_leevel_hpa() { return _sl.pressure; }
|
||||
double get_dewpoint_degc() { return _gl.dewpoint; }
|
||||
double get_dewpoint_sl_degc() { return _sl.dewpoint; }
|
||||
double get_temperature_degc() { return _gl.temperature; }
|
||||
double get_temperature_sea_leevel_degc() { return _sl.temperature; }
|
||||
double get_temperature_mean_degc() { return _gl.temperature_mean; }
|
||||
double get_temperature_mean_sea_level_degc() { return _sl.temperature_mean; }
|
||||
double get_temperature_water_degc() { return _gl.temperature_water; }
|
||||
double get_temperature_seawater_degc() { return _sl.temperature_water; }
|
||||
double get_precipitation_month() { return _gl.precipitation; }
|
||||
double get_precipitation_annual() { return _gl.precipitation_annual; }
|
||||
|
||||
double get_wind_mps() { return _wind_speed; }
|
||||
double get_wind_direction_deg() { return _wind_direction; }
|
||||
|
||||
bool getEnvironmentUpdate() const { return _environment_adjust; }
|
||||
void setEnvironmentUpdate(bool value);
|
||||
|
||||
const char* get_metar() const;
|
||||
|
||||
void test();
|
||||
private:
|
||||
static const std::string _classification[MAX_CLIMATE_CLASSES];
|
||||
static const std::string _description[MAX_CLIMATE_CLASSES];
|
||||
|
||||
#if REPORT_TO_CONSOLE
|
||||
void report();
|
||||
#endif
|
||||
inline void _set(double& prev, double val) {
|
||||
prev = (prev < -1000.0) ? val : 0.99*prev + 0.01*val;
|
||||
}
|
||||
|
||||
// interpolate val (from 0.0 to 1.0) between min and max
|
||||
double daytime(double val, double offset = 0.0);
|
||||
double season(double val, double offset = 0.0);
|
||||
|
||||
double linear(double val, double min, double max);
|
||||
double triangular(double val, double min, double max);
|
||||
double sinusoidal(double val, double min, double max);
|
||||
double even(double val, double min, double max);
|
||||
double long_low(double val, double min, double max);
|
||||
double long_high(double val, double min, double max);
|
||||
double monsoonal(double val, double min, double max);
|
||||
|
||||
void set_ocean();
|
||||
void set_dry();
|
||||
void set_tropical();
|
||||
void set_temperate();
|
||||
void set_continetal();
|
||||
void set_polar();
|
||||
void set_environment();
|
||||
|
||||
void update_daylight();
|
||||
void update_day_factor();
|
||||
void update_season_factor();
|
||||
void update_pressure();
|
||||
void update_wind();
|
||||
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
|
||||
SGPropertyNode_ptr _monthNode;
|
||||
SGPropertyNode_ptr _gravityNode;
|
||||
SGPropertyNode_ptr _metarSnowLevelNode;
|
||||
SGPropertyNode_ptr _positionLatitudeNode;
|
||||
SGPropertyNode_ptr _positionLongitudeNode;
|
||||
|
||||
osg::ref_ptr<osg::Image> image;
|
||||
double _image_width = 0;
|
||||
double _image_height = 0;
|
||||
|
||||
double _epsilon = 1.0;
|
||||
double _prev_lat = -99999.0;
|
||||
double _prev_lon = -99999.0;
|
||||
|
||||
double _sun_latitude_deg = 0.0;
|
||||
double _sun_longitude_deg = 0.0;
|
||||
|
||||
double _adj_latitude_deg = 0.0; // viewer lat adjusted for sun lat
|
||||
double _adj_longitude_deg = 0.0; // viewer lat adjusted for sun lon
|
||||
|
||||
double _daytime = 0.0;
|
||||
double _day_noon = 1.0;
|
||||
double _day_light = 1.0;
|
||||
double _season_summer = 1.0;
|
||||
double _season_transistional = 0.0;
|
||||
double _seasons_year = 0.0;
|
||||
double _is_autumn = -99999.0;
|
||||
|
||||
// Köppen-Geiger classicfications
|
||||
ClimateTile _tiles[3][3];
|
||||
|
||||
// environment
|
||||
bool _environment_adjust = false; // enable automatic adjestments
|
||||
bool _inland_ice_cover = false; // inland water bodies get frozen over
|
||||
double _snow_level = -99999.0; // in meters
|
||||
double _snow_thickness = -99999.0; // 0.0 = thin, 1.0 = thick
|
||||
double _ice_cover = -99999.0; // 0.0 = none, 1.0 = thick
|
||||
double _dust_cover = -99999.0; // 0.0 = none, 1.0 = dusty
|
||||
double _wetness = -99999.0; // 0.0 = dry, 1.0 = wet
|
||||
double _lichen_cover = -99999.0; // 0.0 = none, 1.0 = mossy
|
||||
|
||||
// weather
|
||||
int _code = 0; // Köppen-Geiger classicfication
|
||||
ClimateTile _gl; // ground level parameters
|
||||
|
||||
ClimateTile _sl; // sea level parameters
|
||||
bool _weather_update = false; // enable weather updates
|
||||
double _wind_speed = 0.0; // wind in meters per second
|
||||
double _wind_direction = -99999.0; // wind direction in degrees
|
||||
|
||||
char _metar[256] = "";
|
||||
};
|
||||
|
||||
#endif // _FGCLIMATE_HXX
|
||||
929
src/Environment/environment.cxx
Normal file
929
src/Environment/environment.cxx
Normal file
@@ -0,0 +1,929 @@
|
||||
// environment.cxx -- routines to model the natural environment
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
#include "environment.hxx"
|
||||
#include "atmosphere.hxx"
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Atmosphere model.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef USING_TABLES
|
||||
|
||||
// Calculated based on the ISA standard day, as found at e.g.
|
||||
// http://www.av8n.com/physics/altimetry.htm
|
||||
|
||||
// Each line of data has 3 elements:
|
||||
// Elevation (ft),
|
||||
// temperature factor (dimensionless ratio of absolute temp),
|
||||
// pressure factor (dimensionless ratio)
|
||||
static double atmosphere_data[][3] = {
|
||||
{ -3000.00, 1.021, 1.1133 },
|
||||
{ 0.00, 1.000, 1.0000 },
|
||||
{ 2952.76, 0.980, 0.8978 },
|
||||
{ 5905.51, 0.959, 0.8042 },
|
||||
{ 8858.27, 0.939, 0.7187 },
|
||||
{ 11811.02, 0.919, 0.6407 },
|
||||
{ 14763.78, 0.898, 0.5697 },
|
||||
{ 17716.54, 0.878, 0.5052 },
|
||||
{ 20669.29, 0.858, 0.4468 },
|
||||
{ 23622.05, 0.838, 0.3940 },
|
||||
{ 26574.80, 0.817, 0.3463 },
|
||||
{ 29527.56, 0.797, 0.3034 },
|
||||
{ 32480.31, 0.777, 0.2649 },
|
||||
{ 35433.07, 0.756, 0.2305 },
|
||||
{ 38385.83, 0.752, 0.2000 },
|
||||
{ 41338.58, 0.752, 0.1736 },
|
||||
{ 44291.34, 0.752, 0.1506 },
|
||||
{ 47244.09, 0.752, 0.1307 },
|
||||
{ 50196.85, 0.752, 0.1134 },
|
||||
{ 53149.61, 0.752, 0.0984 },
|
||||
{ 56102.36, 0.752, 0.0854 },
|
||||
{ 59055.12, 0.752, 0.0741 },
|
||||
{ 62007.87, 0.752, 0.0643 },
|
||||
{ 65000.00, 0.752, 0.0557 },
|
||||
{ 68000.00, 0.754, 0.0482 },
|
||||
{ 71000.00, 0.758, 0.0418 },
|
||||
{ 74000.00, 0.761, 0.0362 },
|
||||
{ 77000.00, 0.764, 0.0314 },
|
||||
{ 80000.00, 0.767, 0.0273 },
|
||||
{ 83000.00, 0.770, 0.0237 },
|
||||
{ 86000.00, 0.773, 0.0206 },
|
||||
{ 89000.00, 0.777, 0.0179 },
|
||||
{ 92000.00, 0.780, 0.0156 },
|
||||
{ 95000.00, 0.783, 0.0135 },
|
||||
{ 98000.00, 0.786, 0.0118 },
|
||||
{ 101000.00, 0.789, 0.0103 },
|
||||
{ -1, -1, -1 }
|
||||
};
|
||||
|
||||
static SGInterpTable * _temperature_degc_table = 0;
|
||||
static SGInterpTable * _pressure_inhg_table = 0;
|
||||
|
||||
static void
|
||||
_setup_tables ()
|
||||
{
|
||||
if (_temperature_degc_table != 0)
|
||||
return;
|
||||
|
||||
_temperature_degc_table = new SGInterpTable;
|
||||
_pressure_inhg_table = new SGInterpTable;
|
||||
|
||||
for (int i = 0; atmosphere_data[i][0] != -1; i++) {
|
||||
_temperature_degc_table->addEntry(atmosphere_data[i][0],
|
||||
atmosphere_data[i][1]);
|
||||
_pressure_inhg_table->addEntry(atmosphere_data[i][0],
|
||||
atmosphere_data[i][2]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of FGEnvironment.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FGEnvironment::_init()
|
||||
{
|
||||
live_update = false;
|
||||
elevation_ft = 0;
|
||||
visibility_m = 32000;
|
||||
temperature_sea_level_degc = 15;
|
||||
temperature_degc = 15;
|
||||
dewpoint_sea_level_degc = 5; // guess
|
||||
dewpoint_degc = 5;
|
||||
pressure_sea_level_inhg = 29.92;
|
||||
pressure_inhg = 29.92;
|
||||
density_slugft3 = 0;
|
||||
turbulence_magnitude_norm = 0;
|
||||
turbulence_rate_hz = 1;
|
||||
wind_from_heading_deg = 0;
|
||||
wind_speed_kt = 0;
|
||||
wind_from_north_fps = 0;
|
||||
wind_from_east_fps = 0;
|
||||
wind_from_down_fps = 0;
|
||||
altitude_half_to_sun_m = 1000;
|
||||
altitude_tropo_top_m = 10000;
|
||||
#ifdef USING_TABLES
|
||||
_setup_tables();
|
||||
#endif
|
||||
_recalc_density();
|
||||
_recalc_relative_humidity();
|
||||
live_update = true;
|
||||
}
|
||||
|
||||
FGEnvironment::FGEnvironment()
|
||||
{
|
||||
_init();
|
||||
}
|
||||
|
||||
FGEnvironment::FGEnvironment (const FGEnvironment &env)
|
||||
{
|
||||
_init();
|
||||
copy(env);
|
||||
}
|
||||
|
||||
FGEnvironment::~FGEnvironment()
|
||||
{
|
||||
Untie();
|
||||
}
|
||||
|
||||
FGEnvironment & FGEnvironment::operator = ( const FGEnvironment & other )
|
||||
{
|
||||
copy( other );
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::copy (const FGEnvironment &env)
|
||||
{
|
||||
elevation_ft = env.elevation_ft;
|
||||
visibility_m = env.visibility_m;
|
||||
temperature_sea_level_degc = env.temperature_sea_level_degc;
|
||||
temperature_degc = env.temperature_degc;
|
||||
dewpoint_sea_level_degc = env.dewpoint_sea_level_degc;
|
||||
dewpoint_degc = env.dewpoint_degc;
|
||||
pressure_sea_level_inhg = env.pressure_sea_level_inhg;
|
||||
wind_from_heading_deg = env.wind_from_heading_deg;
|
||||
wind_speed_kt = env.wind_speed_kt;
|
||||
wind_from_north_fps = env.wind_from_north_fps;
|
||||
wind_from_east_fps = env.wind_from_east_fps;
|
||||
wind_from_down_fps = env.wind_from_down_fps;
|
||||
turbulence_magnitude_norm = env.turbulence_magnitude_norm;
|
||||
turbulence_rate_hz = env.turbulence_rate_hz;
|
||||
pressure_inhg = env.pressure_inhg;
|
||||
density_slugft3 = env.density_slugft3;
|
||||
density_tropo_avg_kgm3 = env.density_tropo_avg_kgm3;
|
||||
relative_humidity = env.relative_humidity;
|
||||
altitude_half_to_sun_m = env.altitude_half_to_sun_m;
|
||||
altitude_tropo_top_m = env.altitude_tropo_top_m;
|
||||
live_update = env.live_update;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
maybe_copy_value (FGEnvironment * env, const SGPropertyNode * node,
|
||||
const char * name, void (FGEnvironment::*setter)(double))
|
||||
{
|
||||
const SGPropertyNode * child = node->getNode(name);
|
||||
// fragile: depends on not being typed
|
||||
// as a number
|
||||
if (child != 0 && child->hasValue() &&
|
||||
child->getStringValue()[0] != '\0') {
|
||||
(env->*setter)(child->getDoubleValue());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::read (const SGPropertyNode * node)
|
||||
{
|
||||
bool live_update = set_live_update( false );
|
||||
maybe_copy_value(this, node, "visibility-m",
|
||||
&FGEnvironment::set_visibility_m);
|
||||
|
||||
maybe_copy_value(this, node, "elevation-ft",
|
||||
&FGEnvironment::set_elevation_ft);
|
||||
|
||||
if (!maybe_copy_value(this, node, "temperature-sea-level-degc",
|
||||
&FGEnvironment::set_temperature_sea_level_degc)) {
|
||||
if( maybe_copy_value(this, node, "temperature-degc",
|
||||
&FGEnvironment::set_temperature_degc)) {
|
||||
_recalc_sl_temperature();
|
||||
}
|
||||
}
|
||||
|
||||
if (!maybe_copy_value(this, node, "dewpoint-sea-level-degc",
|
||||
&FGEnvironment::set_dewpoint_sea_level_degc)) {
|
||||
if( maybe_copy_value(this, node, "dewpoint-degc",
|
||||
&FGEnvironment::set_dewpoint_degc)) {
|
||||
_recalc_sl_dewpoint();
|
||||
}
|
||||
}
|
||||
|
||||
if (!maybe_copy_value(this, node, "pressure-sea-level-inhg",
|
||||
&FGEnvironment::set_pressure_sea_level_inhg)) {
|
||||
if( maybe_copy_value(this, node, "pressure-inhg",
|
||||
&FGEnvironment::set_pressure_inhg)) {
|
||||
_recalc_sl_pressure();
|
||||
}
|
||||
}
|
||||
|
||||
maybe_copy_value(this, node, "wind-from-heading-deg",
|
||||
&FGEnvironment::set_wind_from_heading_deg);
|
||||
|
||||
maybe_copy_value(this, node, "wind-speed-kt",
|
||||
&FGEnvironment::set_wind_speed_kt);
|
||||
|
||||
maybe_copy_value(this, node, "turbulence/magnitude-norm",
|
||||
&FGEnvironment::set_turbulence_magnitude_norm);
|
||||
|
||||
maybe_copy_value(this, node, "turbulence/rate-hz",
|
||||
&FGEnvironment::set_turbulence_rate_hz);
|
||||
|
||||
// calculate derived properties here to avoid duplicate expensive computations
|
||||
_recalc_ne();
|
||||
_recalc_alt_pt();
|
||||
_recalc_alt_dewpoint();
|
||||
_recalc_density();
|
||||
_recalc_relative_humidity();
|
||||
|
||||
set_live_update(live_update);
|
||||
}
|
||||
|
||||
void FGEnvironment::Tie( SGPropertyNode_ptr base, bool archivable )
|
||||
{
|
||||
_tiedProperties.setRoot( base );
|
||||
|
||||
_tiedProperties.Tie( "visibility-m", this,
|
||||
&FGEnvironment::get_visibility_m,
|
||||
&FGEnvironment::set_visibility_m);
|
||||
|
||||
_tiedProperties.Tie("elevation-ft", this,
|
||||
&FGEnvironment::get_elevation_ft,
|
||||
&FGEnvironment::set_elevation_ft);
|
||||
|
||||
_tiedProperties.Tie("temperature-sea-level-degc", this,
|
||||
&FGEnvironment::get_temperature_sea_level_degc,
|
||||
&FGEnvironment::set_temperature_sea_level_degc);
|
||||
|
||||
_tiedProperties.Tie("temperature-degc", this,
|
||||
&FGEnvironment::get_temperature_degc,
|
||||
&FGEnvironment::set_temperature_degc);
|
||||
|
||||
_tiedProperties.Tie("dewpoint-sea-level-degc", this,
|
||||
&FGEnvironment::get_dewpoint_sea_level_degc,
|
||||
&FGEnvironment::set_dewpoint_sea_level_degc);
|
||||
|
||||
_tiedProperties.Tie("dewpoint-degc", this,
|
||||
&FGEnvironment::get_dewpoint_degc,
|
||||
&FGEnvironment::set_dewpoint_degc);
|
||||
|
||||
_tiedProperties.Tie("pressure-sea-level-inhg", this,
|
||||
&FGEnvironment::get_pressure_sea_level_inhg,
|
||||
&FGEnvironment::set_pressure_sea_level_inhg);
|
||||
|
||||
_tiedProperties.Tie("pressure-inhg", this,
|
||||
&FGEnvironment::get_pressure_inhg,
|
||||
&FGEnvironment::set_pressure_inhg);
|
||||
|
||||
_tiedProperties.Tie("atmosphere/altitude-half-to-sun", this,
|
||||
&FGEnvironment::get_altitude_half_to_sun_m,
|
||||
&FGEnvironment::set_altitude_half_to_sun_m);
|
||||
|
||||
_tiedProperties.Tie("atmosphere/altitude-troposphere-top", this,
|
||||
&FGEnvironment::get_altitude_tropo_top_m,
|
||||
&FGEnvironment::set_altitude_tropo_top_m);
|
||||
|
||||
_tiedProperties.Tie("wind-from-heading-deg", this,
|
||||
&FGEnvironment::get_wind_from_heading_deg,
|
||||
&FGEnvironment::set_wind_from_heading_deg);
|
||||
|
||||
_tiedProperties.Tie("wind-speed-kt", this,
|
||||
&FGEnvironment::get_wind_speed_kt,
|
||||
&FGEnvironment::set_wind_speed_kt);
|
||||
|
||||
_tiedProperties.Tie("wind-from-north-fps", this,
|
||||
&FGEnvironment::get_wind_from_north_fps,
|
||||
&FGEnvironment::set_wind_from_north_fps);
|
||||
|
||||
_tiedProperties.Tie("wind-from-east-fps", this,
|
||||
&FGEnvironment::get_wind_from_east_fps,
|
||||
&FGEnvironment::set_wind_from_east_fps);
|
||||
|
||||
_tiedProperties.Tie("wind-from-down-fps", this,
|
||||
&FGEnvironment::get_wind_from_down_fps,
|
||||
&FGEnvironment::set_wind_from_down_fps);
|
||||
|
||||
_tiedProperties.Tie("turbulence/magnitude-norm", this,
|
||||
&FGEnvironment::get_turbulence_magnitude_norm,
|
||||
&FGEnvironment::set_turbulence_magnitude_norm);
|
||||
|
||||
_tiedProperties.Tie("turbulence/rate-hz", this,
|
||||
&FGEnvironment::get_turbulence_rate_hz,
|
||||
&FGEnvironment::set_turbulence_rate_hz);
|
||||
|
||||
_tiedProperties.setAttribute( SGPropertyNode::ARCHIVE, archivable );
|
||||
|
||||
_tiedProperties.Tie("temperature-degf", this,
|
||||
&FGEnvironment::get_temperature_degf);
|
||||
|
||||
_tiedProperties.Tie("density-slugft3", this,
|
||||
&FGEnvironment::get_density_slugft3); // read-only
|
||||
|
||||
_tiedProperties.Tie("relative-humidity", this,
|
||||
&FGEnvironment::get_relative_humidity); //ro
|
||||
|
||||
_tiedProperties.Tie("atmosphere/density-tropo-avg", this,
|
||||
&FGEnvironment::get_density_tropo_avg_kgm3); //ro
|
||||
}
|
||||
|
||||
void FGEnvironment::Untie()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_visibility_m () const
|
||||
{
|
||||
return visibility_m;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_temperature_sea_level_degc () const
|
||||
{
|
||||
return temperature_sea_level_degc;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_temperature_degc () const
|
||||
{
|
||||
return temperature_degc;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_temperature_degf () const
|
||||
{
|
||||
return (temperature_degc * 9.0 / 5.0) + 32.0;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_dewpoint_sea_level_degc () const
|
||||
{
|
||||
return dewpoint_sea_level_degc;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_dewpoint_degc () const
|
||||
{
|
||||
return dewpoint_degc;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_pressure_sea_level_inhg () const
|
||||
{
|
||||
return pressure_sea_level_inhg;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_pressure_inhg () const
|
||||
{
|
||||
return pressure_inhg;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_density_slugft3 () const
|
||||
{
|
||||
return density_slugft3;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_relative_humidity () const
|
||||
{
|
||||
return relative_humidity;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_density_tropo_avg_kgm3 () const
|
||||
{
|
||||
return density_tropo_avg_kgm3;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_altitude_half_to_sun_m () const
|
||||
{
|
||||
return altitude_half_to_sun_m;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_altitude_tropo_top_m () const
|
||||
{
|
||||
return altitude_tropo_top_m;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_wind_from_heading_deg () const
|
||||
{
|
||||
return wind_from_heading_deg;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_wind_speed_kt () const
|
||||
{
|
||||
return wind_speed_kt;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_wind_from_north_fps () const
|
||||
{
|
||||
return wind_from_north_fps;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_wind_from_east_fps () const
|
||||
{
|
||||
return wind_from_east_fps;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_wind_from_down_fps () const
|
||||
{
|
||||
return wind_from_down_fps;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_turbulence_magnitude_norm () const
|
||||
{
|
||||
return turbulence_magnitude_norm;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_turbulence_rate_hz () const
|
||||
{
|
||||
return turbulence_rate_hz;
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironment::get_elevation_ft () const
|
||||
{
|
||||
return elevation_ft;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_visibility_m (double v)
|
||||
{
|
||||
visibility_m = v;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_temperature_sea_level_degc (double t)
|
||||
{
|
||||
temperature_sea_level_degc = t;
|
||||
if (dewpoint_sea_level_degc > t)
|
||||
dewpoint_sea_level_degc = t;
|
||||
if( live_update ) {
|
||||
_recalc_alt_pt();
|
||||
_recalc_density();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_temperature_degc (double t)
|
||||
{
|
||||
temperature_degc = t;
|
||||
if( live_update ) {
|
||||
_recalc_sl_temperature();
|
||||
_recalc_sl_pressure();
|
||||
_recalc_alt_pt();
|
||||
_recalc_density();
|
||||
_recalc_relative_humidity();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_dewpoint_sea_level_degc (double t)
|
||||
{
|
||||
dewpoint_sea_level_degc = t;
|
||||
if (temperature_sea_level_degc < t)
|
||||
temperature_sea_level_degc = t;
|
||||
if( live_update ) {
|
||||
_recalc_alt_dewpoint();
|
||||
_recalc_density();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_dewpoint_degc (double t)
|
||||
{
|
||||
dewpoint_degc = t;
|
||||
if( live_update ) {
|
||||
_recalc_sl_dewpoint();
|
||||
_recalc_density();
|
||||
_recalc_relative_humidity();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_pressure_sea_level_inhg (double p)
|
||||
{
|
||||
pressure_sea_level_inhg = p;
|
||||
if( live_update ) {
|
||||
_recalc_alt_pt();
|
||||
_recalc_density();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_pressure_inhg (double p)
|
||||
{
|
||||
pressure_inhg = p;
|
||||
if( live_update ) {
|
||||
_recalc_sl_pressure();
|
||||
_recalc_density();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_wind_from_heading_deg (double h)
|
||||
{
|
||||
wind_from_heading_deg = h;
|
||||
if( live_update ) {
|
||||
_recalc_ne();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_wind_speed_kt (double s)
|
||||
{
|
||||
wind_speed_kt = s;
|
||||
if( live_update ) {
|
||||
_recalc_ne();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_wind_from_north_fps (double n)
|
||||
{
|
||||
wind_from_north_fps = n;
|
||||
if( live_update ) {
|
||||
_recalc_hdgspd();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_wind_from_east_fps (double e)
|
||||
{
|
||||
wind_from_east_fps = e;
|
||||
if( live_update ) {
|
||||
_recalc_hdgspd();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_wind_from_down_fps (double d)
|
||||
{
|
||||
wind_from_down_fps = d;
|
||||
if( live_update ) {
|
||||
_recalc_hdgspd();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_turbulence_magnitude_norm (double t)
|
||||
{
|
||||
turbulence_magnitude_norm = t;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_turbulence_rate_hz (double r)
|
||||
{
|
||||
turbulence_rate_hz = r;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_elevation_ft (double e)
|
||||
{
|
||||
elevation_ft = e;
|
||||
if( live_update ) {
|
||||
_recalc_alt_pt();
|
||||
_recalc_alt_dewpoint();
|
||||
_recalc_density();
|
||||
_recalc_relative_humidity();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_altitude_half_to_sun_m (double alt)
|
||||
{
|
||||
altitude_half_to_sun_m = alt;
|
||||
if( live_update ) {
|
||||
_recalc_density_tropo_avg_kgm3();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::set_altitude_tropo_top_m (double alt)
|
||||
{
|
||||
altitude_tropo_top_m = alt;
|
||||
if( live_update ) {
|
||||
_recalc_density_tropo_avg_kgm3();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_hdgspd ()
|
||||
{
|
||||
wind_from_heading_deg =
|
||||
atan2(wind_from_east_fps, wind_from_north_fps) * SGD_RADIANS_TO_DEGREES;
|
||||
|
||||
if( wind_from_heading_deg < 0 )
|
||||
wind_from_heading_deg += 360.0;
|
||||
|
||||
wind_speed_kt = sqrt(wind_from_north_fps * wind_from_north_fps +
|
||||
wind_from_east_fps * wind_from_east_fps)
|
||||
* SG_METER_TO_NM * SG_FEET_TO_METER * 3600;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_ne ()
|
||||
{
|
||||
double speed_fps =
|
||||
wind_speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET * (1.0/3600);
|
||||
|
||||
wind_from_north_fps = speed_fps *
|
||||
cos(wind_from_heading_deg * SGD_DEGREES_TO_RADIANS);
|
||||
wind_from_east_fps = speed_fps *
|
||||
sin(wind_from_heading_deg * SGD_DEGREES_TO_RADIANS);
|
||||
}
|
||||
|
||||
// Intended to help with the interpretation of METAR data,
|
||||
// not for random in-flight outside-air temperatures.
|
||||
void
|
||||
FGEnvironment::_recalc_sl_temperature ()
|
||||
{
|
||||
|
||||
#if 0
|
||||
{
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "recalc_sl_temperature: using "
|
||||
<< temperature_degc << " @ " << elevation_ft << " :: " << this);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (elevation_ft * atmodel::foot >= ISA_def[1].height) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_ALERT, "recalc_sl_temperature: "
|
||||
<< "valid only in troposphere, not " << elevation_ft);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp: temperature of the stratosphere, in degrees C:
|
||||
double t_strato = ISA_def[1].temp - atmodel::freezing;
|
||||
if (temperature_degc < t_strato) temperature_sea_level_degc = t_strato;
|
||||
else temperature_sea_level_degc =
|
||||
temperature_degc + elevation_ft * atmodel::foot * ISA_def[0].lapse;
|
||||
|
||||
// Alternative implemenation:
|
||||
// else temperature_sea_level_inhg = T_layer(0., elevation_ft * foot,
|
||||
// pressure_inhg * inHg, temperature_degc + freezing, ISA_def[0].lapse) - freezing;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_sl_dewpoint ()
|
||||
{
|
||||
// 0.2degC/1000ft
|
||||
// FIXME: this will work only for low
|
||||
// elevations
|
||||
dewpoint_sea_level_degc = dewpoint_degc + (elevation_ft * .0002);
|
||||
if (dewpoint_sea_level_degc > temperature_sea_level_degc)
|
||||
dewpoint_sea_level_degc = temperature_sea_level_degc;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_alt_dewpoint ()
|
||||
{
|
||||
// 0.2degC/1000ft
|
||||
// FIXME: this will work only for low
|
||||
// elevations
|
||||
dewpoint_degc = dewpoint_sea_level_degc + (elevation_ft * .0002);
|
||||
if (dewpoint_degc > temperature_degc)
|
||||
dewpoint_degc = temperature_degc;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_sl_pressure ()
|
||||
{
|
||||
using namespace atmodel;
|
||||
#if 0
|
||||
{
|
||||
SG_LOG(SG_ENVIRONMENT, SG_ALERT, "recalc_sl_pressure: using "
|
||||
<< pressure_inhg << " and "
|
||||
<< temperature_degc << " @ " << elevation_ft << " :: " << this);
|
||||
}
|
||||
#endif
|
||||
pressure_sea_level_inhg = P_layer(0., elevation_ft * foot,
|
||||
pressure_inhg * inHg, temperature_degc + freezing, ISA_def[0].lapse) / inHg;
|
||||
}
|
||||
|
||||
// This gets called at frame rate, to account for the aircraft's
|
||||
// changing altitude.
|
||||
// Called by set_elevation_ft() which is called by FGEnvironmentMgr::update
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_alt_pt ()
|
||||
{
|
||||
using namespace atmodel;
|
||||
#if 0
|
||||
{
|
||||
static int count(0);
|
||||
if (++count % 1000 == 0) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_ALERT,
|
||||
"recalc_alt_pt for: " << elevation_ft
|
||||
<< " using " << pressure_sea_level_inhg
|
||||
<< " and " << temperature_sea_level_degc
|
||||
<< " :: " << this
|
||||
<< " # " << count);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
double press = pressure_inhg * inHg;
|
||||
double temp = temperature_degc + freezing;
|
||||
std::tie(press, temp) = PT_vs_hpt(elevation_ft * foot,
|
||||
pressure_sea_level_inhg * inHg, temperature_sea_level_degc + freezing);
|
||||
temperature_degc = temp - freezing;
|
||||
pressure_inhg = press / inHg;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_density ()
|
||||
{
|
||||
const double pressure_psf = pressure_inhg * 70.7487;
|
||||
|
||||
// adjust for humidity
|
||||
// calculations taken from USA Today (oops!) at
|
||||
// http://www.usatoday.com/weather/basics/density-calculations.htm
|
||||
const double temperature_degk = temperature_degc + 273.15;
|
||||
const double pressure_mb = pressure_inhg * 33.86;
|
||||
const double vapor_pressure_mb =
|
||||
6.11 * pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
|
||||
|
||||
if ((pressure_mb <= 0.0) || (vapor_pressure_mb <= 0.0)) {
|
||||
density_slugft3 = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
double virtual_temperature_degk = temperature_degk / (1 - (vapor_pressure_mb / pressure_mb) * (1.0 - 0.622));
|
||||
double virtual_temperature_degr = virtual_temperature_degk * 1.8;
|
||||
|
||||
density_slugft3 = pressure_psf / (virtual_temperature_degr * 1718);
|
||||
_recalc_density_tropo_avg_kgm3();
|
||||
}
|
||||
|
||||
// This is used to calculate the average density on the path
|
||||
// of sunlight to the observer for calculating sun-color
|
||||
void
|
||||
FGEnvironment::_recalc_density_tropo_avg_kgm3 ()
|
||||
{
|
||||
const double pressure_mb = pressure_inhg * 33.86;
|
||||
const double vaporpressure = 6.11 * pow(10.0, ((7.5 * dewpoint_degc) / (237.7 + dewpoint_degc)));
|
||||
const double virtual_temp = (temperature_degc + 273.15) / (1 - 0.379 * (vaporpressure/pressure_mb));
|
||||
|
||||
if ((pressure_mb <= 0.0) || (virtual_temp <= 0.0)) {
|
||||
density_tropo_avg_kgm3 = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
double density_half = (100 * pressure_mb * exp(-altitude_half_to_sun_m / 8000))
|
||||
/ (287.05 * virtual_temp);
|
||||
double density_tropo = (100 * pressure_mb * exp((-1 * altitude_tropo_top_m) / 8000))
|
||||
/ ( 287.05 * virtual_temp);
|
||||
|
||||
density_tropo_avg_kgm3 = ((density_slugft3 * 515.379) + density_half + density_tropo) / 3;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironment::_recalc_relative_humidity ()
|
||||
{
|
||||
/*
|
||||
double vaporpressure = 6.11 * pow(10.0, ((7.5 * dewpoint_degc) / ( 237.7 + dewpoint_degc)));
|
||||
double sat_vaporpressure = 6.11 * pow(10.0, ((7.5 * temperature_degc)
|
||||
/ ( 237.7 + temperature_degc)) );
|
||||
relative_humidity = 100 * vaporpressure / sat_vaporpressure ;
|
||||
|
||||
with a little algebra, this gets the same result and spares two multiplications and one pow()
|
||||
*/
|
||||
double a = (7.5 * dewpoint_degc) / ( 237.7 + dewpoint_degc);
|
||||
double b = (7.5 * temperature_degc) / ( 237.7 + temperature_degc);
|
||||
relative_humidity = 100 * pow(10.0,a-b);
|
||||
}
|
||||
|
||||
bool
|
||||
FGEnvironment::set_live_update( bool _live_update )
|
||||
{
|
||||
bool b = live_update;
|
||||
live_update = _live_update;
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Functions.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static inline double
|
||||
do_interp (double a, double b, double fraction)
|
||||
{
|
||||
double retval = (a + ((b - a) * fraction));
|
||||
return retval;
|
||||
}
|
||||
|
||||
static inline double
|
||||
do_interp_deg (double a, double b, double fraction)
|
||||
{
|
||||
a = fmod(a, 360);
|
||||
b = fmod(b, 360);
|
||||
if (fabs(b-a) > 180) {
|
||||
if (a < b)
|
||||
a += 360;
|
||||
else
|
||||
b += 360;
|
||||
}
|
||||
return fmod(do_interp(a, b, fraction), 360);
|
||||
}
|
||||
|
||||
FGEnvironment &
|
||||
FGEnvironment::interpolate( const FGEnvironment & env2,
|
||||
double fraction, FGEnvironment * result) const
|
||||
{
|
||||
// don't calculate each internal property every time we set a single value
|
||||
// we trigger that at the end of the interpolation process
|
||||
bool live_update = result->set_live_update( false );
|
||||
|
||||
result->set_visibility_m
|
||||
(do_interp(get_visibility_m(),
|
||||
env2.get_visibility_m(),
|
||||
fraction));
|
||||
|
||||
result->set_temperature_sea_level_degc
|
||||
(do_interp(get_temperature_sea_level_degc(),
|
||||
env2.get_temperature_sea_level_degc(),
|
||||
fraction));
|
||||
|
||||
result->set_dewpoint_sea_level_degc
|
||||
(do_interp(get_dewpoint_sea_level_degc(),
|
||||
env2.get_dewpoint_sea_level_degc(),
|
||||
fraction));
|
||||
|
||||
result->set_pressure_sea_level_inhg
|
||||
(do_interp(get_pressure_sea_level_inhg(),
|
||||
env2.get_pressure_sea_level_inhg(),
|
||||
fraction));
|
||||
|
||||
result->set_wind_from_heading_deg
|
||||
(do_interp_deg(get_wind_from_heading_deg(),
|
||||
env2.get_wind_from_heading_deg(),
|
||||
fraction));
|
||||
|
||||
result->set_wind_speed_kt
|
||||
(do_interp(get_wind_speed_kt(),
|
||||
env2.get_wind_speed_kt(),
|
||||
fraction));
|
||||
|
||||
result->set_elevation_ft
|
||||
(do_interp(get_elevation_ft(),
|
||||
env2.get_elevation_ft(),
|
||||
fraction));
|
||||
|
||||
result->set_turbulence_magnitude_norm
|
||||
(do_interp(get_turbulence_magnitude_norm(),
|
||||
env2.get_turbulence_magnitude_norm(),
|
||||
fraction));
|
||||
|
||||
result->set_turbulence_rate_hz
|
||||
(do_interp(get_turbulence_rate_hz(),
|
||||
env2.get_turbulence_rate_hz(),
|
||||
fraction));
|
||||
|
||||
// calculate derived properties here to avoid duplicate expensive computations
|
||||
result->_recalc_ne();
|
||||
result->_recalc_alt_pt();
|
||||
result->_recalc_alt_dewpoint();
|
||||
result->_recalc_density();
|
||||
result->_recalc_relative_humidity();
|
||||
|
||||
result->set_live_update(live_update);
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
// end of environment.cxx
|
||||
153
src/Environment/environment.hxx
Normal file
153
src/Environment/environment.hxx
Normal file
@@ -0,0 +1,153 @@
|
||||
// environment.hxx -- routines to model the natural environment.
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef _ENVIRONMENT_HXX
|
||||
#define _ENVIRONMENT_HXX
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
/**
|
||||
* Model the natural environment.
|
||||
*
|
||||
* This class models the natural environment at a specific place and
|
||||
* time. A separate instance is necessary for each location or time.
|
||||
*
|
||||
* This class should eventually move to SimGear.
|
||||
*/
|
||||
class FGEnvironment
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
FGEnvironment();
|
||||
FGEnvironment (const FGEnvironment &environment);
|
||||
virtual ~FGEnvironment();
|
||||
|
||||
FGEnvironment & operator = ( const FGEnvironment & other );
|
||||
|
||||
virtual void read (const SGPropertyNode * node);
|
||||
virtual void Tie( SGPropertyNode_ptr base, bool setArchivable = true );
|
||||
virtual void Untie();
|
||||
|
||||
virtual double get_visibility_m () const;
|
||||
|
||||
virtual double get_temperature_sea_level_degc () const;
|
||||
virtual double get_temperature_degc () const;
|
||||
virtual double get_temperature_degf () const;
|
||||
virtual double get_dewpoint_sea_level_degc () const;
|
||||
virtual double get_dewpoint_degc () const;
|
||||
virtual double get_pressure_sea_level_inhg () const;
|
||||
virtual double get_pressure_inhg () const;
|
||||
virtual double get_density_slugft3 () const;
|
||||
|
||||
virtual double get_relative_humidity () const;
|
||||
virtual double get_density_tropo_avg_kgm3 () const;
|
||||
virtual double get_altitude_half_to_sun_m () const;
|
||||
virtual double get_altitude_tropo_top_m () const;
|
||||
|
||||
virtual double get_wind_from_heading_deg () const;
|
||||
virtual double get_wind_speed_kt () const;
|
||||
virtual double get_wind_from_north_fps () const;
|
||||
virtual double get_wind_from_east_fps () const;
|
||||
virtual double get_wind_from_down_fps () const;
|
||||
|
||||
virtual double get_turbulence_magnitude_norm () const;
|
||||
virtual double get_turbulence_rate_hz () const;
|
||||
|
||||
virtual void set_visibility_m (double v);
|
||||
|
||||
virtual void set_temperature_sea_level_degc (double t);
|
||||
virtual void set_temperature_degc (double t);
|
||||
virtual void set_dewpoint_sea_level_degc (double d);
|
||||
virtual void set_dewpoint_degc (double d);
|
||||
virtual void set_pressure_sea_level_inhg (double p);
|
||||
virtual void set_pressure_inhg (double p);
|
||||
|
||||
virtual void set_wind_from_heading_deg (double h);
|
||||
virtual void set_wind_speed_kt (double s);
|
||||
virtual void set_wind_from_north_fps (double n);
|
||||
virtual void set_wind_from_east_fps (double e);
|
||||
virtual void set_wind_from_down_fps (double d);
|
||||
|
||||
virtual void set_turbulence_magnitude_norm (double t);
|
||||
virtual void set_turbulence_rate_hz (double t);
|
||||
|
||||
virtual double get_elevation_ft () const;
|
||||
virtual void set_elevation_ft (double elevation_ft);
|
||||
virtual void set_altitude_half_to_sun_m (double alt);
|
||||
virtual void set_altitude_tropo_top_m (double alt);
|
||||
|
||||
virtual bool set_live_update(bool live_update);
|
||||
|
||||
|
||||
FGEnvironment & interpolate (const FGEnvironment & env2, double fraction, FGEnvironment * result) const;
|
||||
private:
|
||||
virtual void copy (const FGEnvironment &environment);
|
||||
void _init();
|
||||
void _recalc_hdgspd ();
|
||||
|
||||
void _recalc_sl_temperature ();
|
||||
void _recalc_sl_dewpoint ();
|
||||
void _recalc_sl_pressure ();
|
||||
|
||||
void _recalc_density_tropo_avg_kgm3 ();
|
||||
void _recalc_ne ();
|
||||
void _recalc_alt_dewpoint ();
|
||||
void _recalc_density ();
|
||||
void _recalc_relative_humidity ();
|
||||
void _recalc_alt_pt ();
|
||||
|
||||
double elevation_ft;
|
||||
double visibility_m;
|
||||
|
||||
// Atmosphere
|
||||
double temperature_sea_level_degc;
|
||||
double temperature_degc;
|
||||
double dewpoint_sea_level_degc;
|
||||
double dewpoint_degc;
|
||||
double pressure_sea_level_inhg;
|
||||
double pressure_inhg;
|
||||
double density_slugft3;
|
||||
|
||||
double density_tropo_avg_kgm3;
|
||||
double relative_humidity;
|
||||
double altitude_half_to_sun_m;
|
||||
double altitude_tropo_top_m;
|
||||
|
||||
double turbulence_magnitude_norm;
|
||||
double turbulence_rate_hz;
|
||||
|
||||
double wind_from_heading_deg;
|
||||
double wind_speed_kt;
|
||||
|
||||
double wind_from_north_fps;
|
||||
double wind_from_east_fps;
|
||||
double wind_from_down_fps;
|
||||
|
||||
bool live_update;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
|
||||
};
|
||||
|
||||
#endif // _ENVIRONMENT_HXX
|
||||
378
src/Environment/environment_ctrl.cxx
Normal file
378
src/Environment/environment_ctrl.cxx
Normal file
@@ -0,0 +1,378 @@
|
||||
// environment_ctrl.cxx -- manager for natural environment information.
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
// Partly rewritten by Torsten Dreyer, August 2010.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include "environment_ctrl.hxx"
|
||||
#include "environment.hxx"
|
||||
|
||||
namespace Environment {
|
||||
|
||||
/**
|
||||
* @brief Describes an element of a LayerTable. A defined environment at a given altitude.
|
||||
*/
|
||||
struct LayerTableBucket {
|
||||
double altitude_ft;
|
||||
FGEnvironment environment;
|
||||
inline bool operator< (const LayerTableBucket &b) const {
|
||||
return (altitude_ft < b.altitude_ft);
|
||||
}
|
||||
/**
|
||||
* @brief LessThan predicate for bucket pointers.
|
||||
*/
|
||||
static bool lessThan(LayerTableBucket *a, LayerTableBucket *b) {
|
||||
return (a->altitude_ft) < (b->altitude_ft);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Models a column of our atmosphere by stacking a number of environments above
|
||||
* each other
|
||||
*/
|
||||
class LayerTable : public std::vector<LayerTableBucket *>, public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
LayerTable( SGPropertyNode_ptr rootNode ) :
|
||||
_rootNode(rootNode) {}
|
||||
|
||||
~LayerTable();
|
||||
|
||||
/**
|
||||
* @brief Read the environment column from properties relative to the given root node
|
||||
* @param environment A template environment to copy values from, not given in the configuration
|
||||
*/
|
||||
void read( FGEnvironment * parent = NULL );
|
||||
|
||||
/**
|
||||
*@brief Interpolate and write environment values for a given altitude
|
||||
*@param altitude_ft The altitude for the desired environment
|
||||
*@environment the destination to write the resulting environment properties to
|
||||
*/
|
||||
void interpolate(double altitude_ft, FGEnvironment * environment);
|
||||
|
||||
/**
|
||||
*@brief Bind all environments properties to property nodes and initialize the listeners
|
||||
*/
|
||||
void Bind();
|
||||
|
||||
/**
|
||||
*@brief Unbind all environments properties from property nodes and deregister listeners
|
||||
*/
|
||||
void Unbind();
|
||||
private:
|
||||
/**
|
||||
* @brief Implementation of SGProertyChangeListener::valueChanged()
|
||||
* Takes care of consitent sea level pressure for the entire column
|
||||
*/
|
||||
void valueChanged( SGPropertyNode * node );
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/**
|
||||
*@brief Implementation of the LayerIterpolateController
|
||||
*/
|
||||
class LayerInterpolateControllerImplementation : public LayerInterpolateController
|
||||
{
|
||||
public:
|
||||
LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode );
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void postinit() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double delta_time_sec) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "layer-interpolate-controller"; }
|
||||
|
||||
private:
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
bool _enabled;
|
||||
double _boundary_transition;
|
||||
SGPropertyNode_ptr _altitude_n;
|
||||
SGPropertyNode_ptr _altitude_agl_n;
|
||||
|
||||
LayerTable _boundary_table;
|
||||
LayerTable _aloft_table;
|
||||
|
||||
FGEnvironment _environment;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LayerTable::~LayerTable()
|
||||
{
|
||||
for( iterator it = begin(); it != end(); it++ )
|
||||
delete (*it);
|
||||
}
|
||||
|
||||
void LayerTable::read(FGEnvironment * parent )
|
||||
{
|
||||
double last_altitude_ft = 0.0;
|
||||
double sort_required = false;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < (size_t)_rootNode->nChildren(); i++) {
|
||||
const SGPropertyNode * child = _rootNode->getChild(i);
|
||||
if ( child->getNameString() == "entry"
|
||||
&& child->getStringValue("elevation-ft", "")[0] != '\0'
|
||||
&& ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
|
||||
{
|
||||
LayerTableBucket * b;
|
||||
if( i < size() ) {
|
||||
// recycle existing bucket
|
||||
b = at(i);
|
||||
} else {
|
||||
// more nodes than buckets in table, add a new one
|
||||
b = new LayerTableBucket;
|
||||
push_back(b);
|
||||
}
|
||||
if (i == 0 && parent != NULL )
|
||||
b->environment = *parent;
|
||||
if (i > 0)
|
||||
b->environment = at(i-1)->environment;
|
||||
|
||||
b->environment.read(child);
|
||||
b->altitude_ft = b->environment.get_elevation_ft();
|
||||
|
||||
// check, if altitudes are in ascending order
|
||||
if( b->altitude_ft < last_altitude_ft )
|
||||
sort_required = true;
|
||||
last_altitude_ft = b->altitude_ft;
|
||||
}
|
||||
}
|
||||
// remove leftover buckets
|
||||
while( size() > i ) {
|
||||
LayerTableBucket * b = *(end() - 1);
|
||||
delete b;
|
||||
pop_back();
|
||||
}
|
||||
|
||||
if( sort_required )
|
||||
sort(begin(), end(), LayerTableBucket::lessThan);
|
||||
|
||||
// cleanup entries with (almost)same altitude
|
||||
for( size_type n = 1; n < size(); n++ ) {
|
||||
if( fabs(at(n)->altitude_ft - at(n-1)->altitude_ft ) < 1 ) {
|
||||
SG_LOG( SG_ENVIRONMENT, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << at(n)->altitude_ft );
|
||||
erase( begin() + n );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayerTable::Bind()
|
||||
{
|
||||
// tie all environments to ~/entry[n]/xxx
|
||||
// register this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
|
||||
// and ~/entry[n]/elevation-ft
|
||||
for( unsigned i = 0; i < size(); i++ ) {
|
||||
SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
|
||||
at(i)->environment.Tie( baseNode );
|
||||
baseNode->getNode( "pressure-sea-level-inhg", true )->addChangeListener( this );
|
||||
baseNode->getNode("elevation-ft", true)->addChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerTable::Unbind()
|
||||
{
|
||||
// untie all environments to ~/entry[n]/xxx
|
||||
// deregister this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
|
||||
// and ~/entry[n]/elevation-ft
|
||||
for( unsigned i = 0; i < size(); i++ ) {
|
||||
SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
|
||||
at(i)->environment.Untie();
|
||||
baseNode->getNode( "pressure-sea-level-inhg", true )->removeChangeListener( this );
|
||||
baseNode->getNode("elevation-ft", true)->removeChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
void LayerTable::valueChanged( SGPropertyNode * node )
|
||||
{
|
||||
// - Make sure all environments in our column use the same sea level pressure
|
||||
// - Synchronize layer elevations
|
||||
if (node->getNameString() == "pressure-sea-level-inhg") {
|
||||
double value = node->getDoubleValue();
|
||||
for (iterator it = begin(); it != end(); it++) {
|
||||
(*it)->environment.set_pressure_sea_level_inhg(value);
|
||||
}
|
||||
} else {
|
||||
bool sort_required = false;
|
||||
double last_altitude_ft = 0.0;
|
||||
for (iterator it = begin(); it != end(); it++) {
|
||||
(*it)->altitude_ft = (*it)->environment.get_elevation_ft();
|
||||
if ((*it)->altitude_ft < last_altitude_ft)
|
||||
sort_required = true;
|
||||
last_altitude_ft = (*it)->altitude_ft;
|
||||
}
|
||||
if (sort_required)
|
||||
sort(begin(), end(), LayerTableBucket::lessThan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayerTable::interpolate( double altitude_ft, FGEnvironment * result )
|
||||
{
|
||||
int length = size();
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
// Boundary conditions
|
||||
if ((length == 1) || (at(0)->altitude_ft >= altitude_ft)) {
|
||||
*result = at(0)->environment; // below bottom of table
|
||||
return;
|
||||
} else if (at(length-1)->altitude_ft <= altitude_ft) {
|
||||
*result = at(length-1)->environment; // above top of table
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the interpolation table
|
||||
int layer;
|
||||
for ( layer = 1; // can't be below bottom layer, handled above
|
||||
layer < length && at(layer)->altitude_ft <= altitude_ft;
|
||||
layer++);
|
||||
FGEnvironment & env1 = (at(layer-1)->environment);
|
||||
FGEnvironment & env2 = (at(layer)->environment);
|
||||
// two layers of same altitude were sorted out in read_table
|
||||
double fraction = ((altitude_ft - at(layer-1)->altitude_ft) /
|
||||
(at(layer)->altitude_ft - at(layer-1)->altitude_ft));
|
||||
env1.interpolate(env2, fraction, result);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LayerInterpolateControllerImplementation::LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode ) :
|
||||
_rootNode( rootNode ),
|
||||
_enabled(true),
|
||||
_boundary_transition(0.0),
|
||||
_altitude_n( fgGetNode("/position/altitude-ft", true)),
|
||||
_altitude_agl_n( fgGetNode("/position/altitude-agl-ft", true)),
|
||||
_boundary_table( rootNode->getNode("boundary", true ) ),
|
||||
_aloft_table( rootNode->getNode("aloft", true ) )
|
||||
{
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::init ()
|
||||
{
|
||||
_boundary_table.read();
|
||||
// pass in a pointer to the environment of the last bondary layer as
|
||||
// a starting point
|
||||
_aloft_table.read(&(*(_boundary_table.end()-1))->environment);
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::reinit ()
|
||||
{
|
||||
_boundary_table.Unbind();
|
||||
_aloft_table.Unbind();
|
||||
init();
|
||||
postinit();
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::postinit()
|
||||
{
|
||||
// we get here after 1. bind() and 2. init() was called by fg_init
|
||||
_boundary_table.Bind();
|
||||
_aloft_table.Bind();
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::bind()
|
||||
{
|
||||
// don't bind the layer tables here, because they have not been read in yet.
|
||||
_environment.Tie( _rootNode->getNode( "interpolated", true ) );
|
||||
_tiedProperties.Tie( _rootNode->getNode("enabled", true), &_enabled );
|
||||
_tiedProperties.Tie( _rootNode->getNode("boundary-transition-ft", true ), &_boundary_transition );
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::unbind()
|
||||
{
|
||||
_boundary_table.Unbind();
|
||||
_aloft_table.Unbind();
|
||||
_tiedProperties.Untie();
|
||||
_environment.Untie();
|
||||
}
|
||||
|
||||
void LayerInterpolateControllerImplementation::update (double delta_time_sec)
|
||||
{
|
||||
if( !_enabled || delta_time_sec <= SGLimitsd::min() )
|
||||
return;
|
||||
|
||||
double altitude_ft = _altitude_n->getDoubleValue();
|
||||
double altitude_agl_ft = _altitude_agl_n->getDoubleValue();
|
||||
|
||||
// avoid div by zero later on and init with a default value if not given
|
||||
if( _boundary_transition <= SGLimitsd::min() )
|
||||
_boundary_transition = 500;
|
||||
|
||||
int length = _boundary_table.size();
|
||||
|
||||
if (length > 0) {
|
||||
// If a boundary table is defined, get the top of the boundary layer
|
||||
double boundary_limit = _boundary_table[length-1]->altitude_ft;
|
||||
if (boundary_limit >= altitude_agl_ft) {
|
||||
// If current altitude is below top of boundary layer, interpolate
|
||||
// only in boundary layer
|
||||
_boundary_table.interpolate(altitude_agl_ft, &_environment);
|
||||
return;
|
||||
} else if ((boundary_limit + _boundary_transition) >= altitude_agl_ft) {
|
||||
// If current altitude is above top of boundary layer and within the
|
||||
// transition altitude, interpolate boundary and aloft layers
|
||||
FGEnvironment env1, env2;
|
||||
_boundary_table.interpolate( altitude_agl_ft, &env1);
|
||||
_aloft_table.interpolate(altitude_ft, &env2);
|
||||
double fraction = (altitude_agl_ft - boundary_limit) / _boundary_transition;
|
||||
env1.interpolate(env2, fraction, &_environment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If no boundary layer is defined or altitude is above top boundary-layer plus boundary-transition
|
||||
// altitude, use only the aloft table
|
||||
_aloft_table.interpolate( altitude_ft, &_environment);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LayerInterpolateController * LayerInterpolateController::createInstance( SGPropertyNode_ptr rootNode )
|
||||
{
|
||||
return new LayerInterpolateControllerImplementation( rootNode );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Register the subsystem.
|
||||
#if 0
|
||||
SGSubsystemMgr::Registrant<LayerInterpolateControllerImplementation> registrantLayerInterpolateControllerImplementation;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
37
src/Environment/environment_ctrl.hxx
Normal file
37
src/Environment/environment_ctrl.hxx
Normal file
@@ -0,0 +1,37 @@
|
||||
// environment-ctrl.hxx -- controller for environment information.
|
||||
//
|
||||
// Written by David Megginson, started May 2002.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef _ENVIRONMENT_CTRL_HXX
|
||||
#define _ENVIRONMENT_CTRL_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
class LayerInterpolateController : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
static LayerInterpolateController * createInstance( SGPropertyNode_ptr rootNode );
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // _ENVIRONMENT_CTRL_HXX
|
||||
587
src/Environment/environment_mgr.cxx
Normal file
587
src/Environment/environment_mgr.cxx
Normal file
@@ -0,0 +1,587 @@
|
||||
// environment-mgr.cxx -- manager for natural environment information.
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include <simgear/scene/sky/sky.hxx>
|
||||
#include <simgear/scene/model/particles.hxx>
|
||||
#include <simgear/structure/event_mgr.hxx>
|
||||
|
||||
#include <Main/main.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Viewer/ViewPropertyEvaluator.hxx>
|
||||
|
||||
#include <FDM/flight.hxx>
|
||||
|
||||
#include "environment.hxx"
|
||||
#include "environment_mgr.hxx"
|
||||
#include "environment_ctrl.hxx"
|
||||
#include "realwx_ctrl.hxx"
|
||||
#include "fgclouds.hxx"
|
||||
#include "precipitation_mgr.hxx"
|
||||
#include "ridge_lift.hxx"
|
||||
#include "terrainsampler.hxx"
|
||||
#include "Airports/airport.hxx"
|
||||
#include "gravity.hxx"
|
||||
#include "climate.hxx"
|
||||
#include "magvarmanager.hxx"
|
||||
|
||||
#include "AIModel/AINotifications.hxx"
|
||||
|
||||
class FG3DCloudsListener : public SGPropertyChangeListener {
|
||||
public:
|
||||
FG3DCloudsListener( FGClouds * fgClouds );
|
||||
virtual ~FG3DCloudsListener();
|
||||
|
||||
virtual void valueChanged (SGPropertyNode * node);
|
||||
|
||||
private:
|
||||
FGClouds * _fgClouds;
|
||||
SGPropertyNode_ptr _enableNode;
|
||||
};
|
||||
|
||||
FG3DCloudsListener::FG3DCloudsListener( FGClouds * fgClouds ) :
|
||||
_fgClouds( fgClouds )
|
||||
{
|
||||
_enableNode = fgGetNode( "/sim/rendering/clouds3d-enable", true );
|
||||
_enableNode->addChangeListener( this );
|
||||
|
||||
valueChanged( _enableNode );
|
||||
}
|
||||
|
||||
FG3DCloudsListener::~FG3DCloudsListener()
|
||||
{
|
||||
_enableNode->removeChangeListener( this );
|
||||
}
|
||||
|
||||
void FG3DCloudsListener::valueChanged( SGPropertyNode * node )
|
||||
{
|
||||
_fgClouds->set_3dClouds( _enableNode->getBoolValue() );
|
||||
}
|
||||
|
||||
FGEnvironmentMgr::FGEnvironmentMgr () :
|
||||
_environment(new FGEnvironment()),
|
||||
_multiplayerListener(nullptr),
|
||||
_sky(globals->get_renderer()->getSky()),
|
||||
nearestCarrier(nullptr),
|
||||
nearestAirport(nullptr)
|
||||
{
|
||||
fgClouds = new FGClouds;
|
||||
_3dCloudsEnableListener = new FG3DCloudsListener(fgClouds);
|
||||
set_subsystem("controller", Environment::LayerInterpolateController::createInstance( fgGetNode("/environment/config", true ) ));
|
||||
|
||||
set_subsystem("climate", new FGClimate);
|
||||
set_subsystem("precipitation", new FGPrecipitationMgr);
|
||||
set_subsystem("realwx", Environment::RealWxController::createInstance( fgGetNode("/environment/realwx", true ) ), 1.0 );
|
||||
set_subsystem("terrainsampler", Environment::TerrainSampler::createInstance( fgGetNode("/environment/terrain", true ) ));
|
||||
set_subsystem("ridgelift", new FGRidgeLift);
|
||||
|
||||
set_subsystem("magvar", new FGMagVarManager);
|
||||
max_tower_height_feet = fgGetDouble("/sim/airport/max-tower-height-ft", 70);
|
||||
min_tower_height_feet = fgGetDouble("/sim/airport/min-tower-height-ft", 6);
|
||||
default_tower_height_feet = fgGetDouble("default-tower-height-ft", 30);
|
||||
}
|
||||
|
||||
FGEnvironmentMgr::~FGEnvironmentMgr ()
|
||||
{
|
||||
remove_subsystem( "ridgelift" );
|
||||
remove_subsystem( "terrainsampler" );
|
||||
remove_subsystem("precipitation");
|
||||
remove_subsystem("realwx");
|
||||
remove_subsystem("controller");
|
||||
remove_subsystem("magvar");
|
||||
|
||||
delete fgClouds;
|
||||
delete _3dCloudsEnableListener;
|
||||
delete _environment;
|
||||
}
|
||||
|
||||
struct FGEnvironmentMgrMultiplayerListener : SGPropertyChangeListener {
|
||||
FGEnvironmentMgrMultiplayerListener(FGEnvironmentMgr* environmentmgr)
|
||||
:
|
||||
_environmentmgr(environmentmgr)
|
||||
{
|
||||
_node = fgGetNode("/sim/current-view/model-view", true /*create*/);
|
||||
_node->addChangeListener(this);
|
||||
}
|
||||
virtual void valueChanged(SGPropertyNode* node)
|
||||
{
|
||||
_environmentmgr->updateClosestAirport();
|
||||
}
|
||||
virtual ~FGEnvironmentMgrMultiplayerListener()
|
||||
{
|
||||
_node->removeChangeListener(this);
|
||||
}
|
||||
private:
|
||||
FGEnvironmentMgr* _environmentmgr;
|
||||
SGPropertyNode_ptr _node;
|
||||
};
|
||||
|
||||
SGSubsystem::InitStatus FGEnvironmentMgr::incrementalInit()
|
||||
{
|
||||
|
||||
InitStatus r = SGSubsystemGroup::incrementalInit();
|
||||
if (r == INIT_DONE) {
|
||||
fgClouds->Init();
|
||||
_multiplayerListener = new FGEnvironmentMgrMultiplayerListener(this);
|
||||
globals->get_event_mgr()->addTask("updateClosestAirport",
|
||||
[this](){ this->updateClosestAirport(); }, 10 );
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::shutdown()
|
||||
{
|
||||
globals->get_event_mgr()->removeTask("updateClosestAirport");
|
||||
delete _multiplayerListener;
|
||||
_multiplayerListener = nullptr;
|
||||
SGSubsystemGroup::shutdown();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::reinit ()
|
||||
{
|
||||
SG_LOG( SG_ENVIRONMENT, SG_INFO, "Reinitializing environment subsystem");
|
||||
SGSubsystemGroup::reinit();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::bind ()
|
||||
{
|
||||
SGSubsystemGroup::bind();
|
||||
_environment->Tie( fgGetNode("/environment", true ) );
|
||||
|
||||
_tiedProperties.setRoot( fgGetNode( "/environment", true ) );
|
||||
|
||||
_tiedProperties.Tie( "effective-visibility-m", _sky,
|
||||
&SGSky::get_visibility );
|
||||
|
||||
_tiedProperties.Tie("rebuild-layers", fgClouds,
|
||||
&FGClouds::get_update_event,
|
||||
&FGClouds::set_update_event);
|
||||
// _tiedProperties.Tie("turbulence/use-cloud-turbulence", &sgEnviro,
|
||||
// &SGEnviro::get_turbulence_enable_state,
|
||||
// &SGEnviro::set_turbulence_enable_state);
|
||||
|
||||
for (int i = 0; i < MAX_CLOUD_LAYERS; i++) {
|
||||
SGPropertyNode_ptr layerNode = fgGetNode("/environment/clouds",true)->getChild("layer", i, true );
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("span-m",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_span_m,
|
||||
&FGEnvironmentMgr::set_cloud_layer_span_m);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("elevation-ft",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_elevation_ft,
|
||||
&FGEnvironmentMgr::set_cloud_layer_elevation_ft);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("thickness-ft",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_thickness_ft,
|
||||
&FGEnvironmentMgr::set_cloud_layer_thickness_ft);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("transition-ft",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_transition_ft,
|
||||
&FGEnvironmentMgr::set_cloud_layer_transition_ft);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("coverage",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_coverage,
|
||||
&FGEnvironmentMgr::set_cloud_layer_coverage);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode("coverage-type",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_coverage_type,
|
||||
&FGEnvironmentMgr::set_cloud_layer_coverage_type);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode( "visibility-m",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_visibility_m,
|
||||
&FGEnvironmentMgr::set_cloud_layer_visibility_m);
|
||||
|
||||
_tiedProperties.Tie( layerNode->getNode( "alpha",true), this, i,
|
||||
&FGEnvironmentMgr::get_cloud_layer_maxalpha,
|
||||
&FGEnvironmentMgr::set_cloud_layer_maxalpha);
|
||||
}
|
||||
|
||||
_tiedProperties.setRoot( fgGetNode("/sim/rendering", true ) );
|
||||
|
||||
_tiedProperties.Tie( "clouds3d-density", _sky,
|
||||
&SGSky::get_3dCloudDensity,
|
||||
&SGSky::set_3dCloudDensity);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-vis-range", _sky,
|
||||
&SGSky::get_3dCloudVisRange,
|
||||
&SGSky::set_3dCloudVisRange);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-impostor-range", _sky,
|
||||
&SGSky::get_3dCloudImpostorDistance,
|
||||
&SGSky::set_3dCloudImpostorDistance);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-lod1-range", _sky,
|
||||
&SGSky::get_3dCloudLoD1Range,
|
||||
&SGSky::set_3dCloudLoD1Range);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-lod2-range", _sky,
|
||||
&SGSky::get_3dCloudLoD2Range,
|
||||
&SGSky::set_3dCloudLoD2Range);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-wrap", _sky,
|
||||
&SGSky::get_3dCloudWrap,
|
||||
&SGSky::set_3dCloudWrap);
|
||||
|
||||
_tiedProperties.Tie("clouds3d-use-impostors", _sky,
|
||||
&SGSky::get_3dCloudUseImpostors,
|
||||
&SGSky::set_3dCloudUseImpostors);
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::unbind ()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
_environment->Untie();
|
||||
SGSubsystemGroup::unbind();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::update (double dt)
|
||||
{
|
||||
SGGeod aircraftPos(globals->get_aircraft_position());
|
||||
|
||||
SGSubsystemGroup::update(dt);
|
||||
|
||||
_environment->set_elevation_ft( aircraftPos.getElevationFt() );
|
||||
|
||||
auto particlesManager = simgear::ParticlesGlobalManager::instance();
|
||||
particlesManager->setWindFrom(_environment->get_wind_from_heading_deg(),
|
||||
_environment->get_wind_speed_kt());
|
||||
particlesManager->update(dt, globals->get_aircraft_position());
|
||||
|
||||
if( _cloudLayersDirty ) {
|
||||
_cloudLayersDirty = false;
|
||||
fgClouds->set_update_event( fgClouds->get_update_event()+1 );
|
||||
}
|
||||
updateTowerPosition();
|
||||
|
||||
fgSetDouble( "/environment/gravitational-acceleration-mps2",
|
||||
Environment::Gravity::instance()->getGravity(aircraftPos));
|
||||
}
|
||||
|
||||
void FGEnvironmentMgr::updateTowerPosition()
|
||||
{
|
||||
if (towerViewPositionLatDegNode != nullptr && towerViewPositionLonDegNode != nullptr && towerViewPositionAltFtNode != nullptr) {
|
||||
auto automaticTowerActive = fgGetBool("/sim/tower/auto-position", true);
|
||||
|
||||
fgSetDouble("/sim/airport/nearest-tower-latitude-deg", towerViewPositionLatDegNode->getDoubleValue());
|
||||
fgSetDouble("/sim/airport/nearest-tower-longitude-deg", towerViewPositionLonDegNode->getDoubleValue());
|
||||
fgSetDouble("/sim/airport/nearest-tower-altitude-ft", towerViewPositionAltFtNode->getDoubleValue());
|
||||
|
||||
if (automaticTowerActive) {
|
||||
fgSetDouble("/sim/tower/latitude-deg", towerViewPositionLatDegNode->getDoubleValue());
|
||||
fgSetDouble("/sim/tower/longitude-deg", towerViewPositionLonDegNode->getDoubleValue());
|
||||
fgSetDouble("/sim/tower/altitude-ft", towerViewPositionAltFtNode->getDoubleValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGEnvironmentMgr::updateClosestAirport()
|
||||
{
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "FGEnvironmentMgr::update: updating closest airport");
|
||||
|
||||
SGGeod pos = globals->get_aircraft_position();
|
||||
|
||||
//
|
||||
// If we are viewing a multiplayer aircraft, find nearest airport so that
|
||||
// Tower View etc works.
|
||||
std::string view_config_root = ViewPropertyEvaluator::getStringValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/root)");
|
||||
|
||||
if (view_config_root != "/" && view_config_root != "") {
|
||||
/* We are currently viewing a multiplayer aircraft. */
|
||||
pos = SGGeod::fromDegFt(
|
||||
ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/longitude-deg)"),
|
||||
ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/latitude-deg)"),
|
||||
ViewPropertyEvaluator::getDoubleValue("((/sim/view[(/sim/current-view/view-number-raw)]/config/root)/position/altitude-ft)"));
|
||||
}
|
||||
|
||||
// nearest tower logic;
|
||||
// 1. find nearest airport
|
||||
// 2. find nearest carrier
|
||||
// - select the nearest one as the tower.
|
||||
|
||||
nearestAirport = FGAirport::findClosest(pos, 100.0);
|
||||
auto automaticTowerActive = fgGetBool("/sim/tower/auto-position", true);
|
||||
|
||||
SGGeod nearestTowerPosition;
|
||||
std::string nearestIdent;
|
||||
const SGGeod airportGeod;
|
||||
double towerDistance = numeric_limits<double>::max();
|
||||
if (nearestAirport) {
|
||||
const string currentId = fgGetString("/sim/airport/closest-airport-id", "");
|
||||
if (currentId != nearestAirport->ident()) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "FGEnvironmentMgr::updateClosestAirport: selected:" << nearestAirport->ident());
|
||||
fgSetString("/sim/airport/closest-airport-id", nearestAirport->ident().c_str());
|
||||
}
|
||||
|
||||
if (nearestAirport->hasTower()) {
|
||||
nearestTowerPosition = nearestAirport->getTowerLocation();
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "airport-id=" << nearestAirport->getId() << " tower_pos=" << nearestTowerPosition);
|
||||
}
|
||||
else {
|
||||
nearestTowerPosition = nearestAirport->geod();
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "no tower for airport-id=" << nearestAirport->getId());
|
||||
}
|
||||
//Ensure that the tower isn't at ground level by adding a nominal amount
|
||||
//TODO: (fix the data so that too short or too tall towers aren't present in the data)
|
||||
auto towerAirpotDistance = abs(nearestTowerPosition.getElevationFt() - nearestAirport->geod().getElevationFt());
|
||||
if (towerAirpotDistance < min_tower_height_feet) {
|
||||
nearestTowerPosition.setElevationFt(nearestTowerPosition.getElevationFt() + default_tower_height_feet);
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Tower altitude adjusted because it was at below minimum height above ground (" << min_tower_height_feet << "feet) for airport " << nearestAirport->getId());
|
||||
}
|
||||
else if (towerAirpotDistance > max_tower_height_feet) {
|
||||
nearestTowerPosition.setElevationFt(nearestTowerPosition.getElevationFt() + default_tower_height_feet);
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Tower altitude adjusted because it was taller than the permitted maximum of (" << max_tower_height_feet << "feet) for airport " << nearestAirport->getId());
|
||||
}
|
||||
//
|
||||
nearestIdent = nearestAirport->ident();
|
||||
towerDistance = SGGeodesy::distanceM(nearestTowerPosition, pos);
|
||||
|
||||
// when the tower doesn't move we can clear these.
|
||||
// if the carrier is nearer these variables will be set in that logic.
|
||||
towerViewPositionLatDegNode = towerViewPositionLonDegNode = towerViewPositionAltFtNode = nullptr;
|
||||
}
|
||||
else {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "FGEnvironmentMgr::update: No airport within 100NM range");
|
||||
}
|
||||
auto nctn = SGSharedPtr< NearestCarrierToNotification> (new NearestCarrierToNotification(pos));
|
||||
if (simgear::Emesary::ReceiptStatus::OK == simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(nctn)) {
|
||||
if (nearestCarrier != nctn->GetCarrier()) {
|
||||
nearestCarrier = nctn->GetCarrier();
|
||||
fgSetString("/sim/airport/nearest-carrier", nctn->GetCarrierIdent());
|
||||
}
|
||||
} else {
|
||||
fgSetString("/sim/airport/nearest-carrier", "");
|
||||
fgSetDouble("/sim/airport/nearest-carrier-latitude-deg", 0);
|
||||
fgSetDouble("/sim/airport/nearest-carrier-longitude-deg", 0);
|
||||
fgSetDouble("/sim/airport/nearest-carrier-altitude-ft", 0);
|
||||
fgSetDouble("/sim/airport/nearest-carrier-deck-height", 0);
|
||||
nearestCarrier = nullptr;
|
||||
}
|
||||
|
||||
// figure out if the carrier's tower is closer
|
||||
if (nearestCarrier && (nctn->GetDistanceMeters() < towerDistance)) {
|
||||
nearestIdent = nctn->GetCarrierIdent();
|
||||
|
||||
//
|
||||
// these will be used to determine and update the tower position
|
||||
towerViewPositionLatDegNode = nctn->GetViewPositionLatNode();
|
||||
towerViewPositionLonDegNode = nctn->GetViewPositionLonNode();
|
||||
towerViewPositionAltFtNode = nctn->GetViewPositionAltNode();
|
||||
|
||||
// although the carrier is moving - these values can afford to be 10 seconds old so we don't need to
|
||||
// update them.
|
||||
fgSetDouble("/sim/airport/nearest-carrier-latitude-deg", nctn->GetPosition()->getLatitudeDeg());
|
||||
fgSetDouble("/sim/airport/nearest-carrier-longitude-deg", nctn->GetPosition()->getLongitudeDeg());
|
||||
fgSetDouble("/sim/airport/nearest-carrier-altitude-ft", nctn->GetPosition()->getElevationFt());
|
||||
fgSetDouble("/sim/airport/nearest-carrier-deck-height", nctn->GetDeckheight());
|
||||
} else {
|
||||
if (nearestAirport != nullptr) {
|
||||
std::string path = ViewPropertyEvaluator::getStringValue("(/sim/view[(/sim/current-view/view-number-raw)]/config/root)/sim/tower/");
|
||||
fgSetString(path + "airport-id", nearestAirport->getId());
|
||||
|
||||
fgSetDouble(path + "latitude-deg", nearestTowerPosition.getLatitudeDeg());
|
||||
fgSetDouble(path + "longitude-deg", nearestTowerPosition.getLongitudeDeg());
|
||||
fgSetDouble(path + "altitude-ft", nearestTowerPosition.getElevationFt());
|
||||
}
|
||||
else {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "FGEnvironmentMgr::update: No airport or carrier within 100NM range of current multiplayer aircraft");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fgGetString("/sim/airport/nearest-tower-ident") != nearestIdent) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "Nearest airport tower now " << nearestIdent);
|
||||
fgSetString("/sim/airport/nearest-tower-ident", nearestIdent);
|
||||
}
|
||||
if (automaticTowerActive) {
|
||||
if (fgGetString("/sim/tower/airport-id") != nearestIdent) {
|
||||
fgSetString("/sim/tower/airport-id", nearestIdent);
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "Auto Tower: now " << nearestIdent);
|
||||
}
|
||||
}
|
||||
updateTowerPosition();
|
||||
}
|
||||
|
||||
|
||||
FGEnvironment
|
||||
FGEnvironmentMgr::getEnvironment () const
|
||||
{
|
||||
return *_environment;
|
||||
}
|
||||
|
||||
const FGEnvironment* FGEnvironmentMgr::getAircraftEnvironment() const
|
||||
{
|
||||
return _environment;
|
||||
}
|
||||
|
||||
FGEnvironment
|
||||
FGEnvironmentMgr::getEnvironmentAtPosition(const SGGeod& aPos) const
|
||||
{
|
||||
// Always returns the same environment
|
||||
// for now; we'll make it interesting
|
||||
// later.
|
||||
FGEnvironment env = *_environment;
|
||||
env.set_elevation_ft(aPos.getElevationFt());
|
||||
return env;
|
||||
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_span_m (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getSpan_m();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_span_m (int index, double span_m)
|
||||
{
|
||||
_sky->get_cloud_layer(index)->setSpan_m(span_m);
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_elevation_ft (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getElevation_m() * SG_METER_TO_FEET;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_elevation_ft (int index, double elevation_ft)
|
||||
{
|
||||
FGEnvironment env = *_environment;
|
||||
env.set_elevation_ft(elevation_ft);
|
||||
|
||||
_sky->get_cloud_layer(index)
|
||||
->setElevation_m(elevation_ft * SG_FEET_TO_METER);
|
||||
|
||||
_sky->get_cloud_layer(index)
|
||||
->setSpeed(env.get_wind_speed_kt() * 0.5151); // 1 kt = 0.5151 m/s
|
||||
|
||||
_sky->get_cloud_layer(index)
|
||||
->setDirection(env.get_wind_from_heading_deg());
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_thickness_ft (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getThickness_m() * SG_METER_TO_FEET;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_thickness_ft (int index, double thickness_ft)
|
||||
{
|
||||
_sky->get_cloud_layer(index)
|
||||
->setThickness_m(thickness_ft * SG_FEET_TO_METER);
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_transition_ft (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getTransition_m() * SG_METER_TO_FEET;
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_transition_ft (int index,
|
||||
double transition_ft)
|
||||
{
|
||||
_sky->get_cloud_layer(index)
|
||||
->setTransition_m(transition_ft * SG_FEET_TO_METER);
|
||||
}
|
||||
|
||||
const char *
|
||||
FGEnvironmentMgr::get_cloud_layer_coverage (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getCoverageString().c_str();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_coverage (int index,
|
||||
const char * coverage_name)
|
||||
{
|
||||
if( _sky->get_cloud_layer(index)->getCoverageString() == coverage_name )
|
||||
return;
|
||||
|
||||
_sky->get_cloud_layer(index)->setCoverageString(coverage_name);
|
||||
_cloudLayersDirty = true;
|
||||
}
|
||||
|
||||
int
|
||||
FGEnvironmentMgr::get_cloud_layer_coverage_type (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getCoverage();
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_visibility_m (int index) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getVisibility_m();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_visibility_m (int index, double visibility_m)
|
||||
{
|
||||
_sky->get_cloud_layer(index)->setVisibility_m(visibility_m);
|
||||
}
|
||||
|
||||
double
|
||||
FGEnvironmentMgr::get_cloud_layer_maxalpha (int index ) const
|
||||
{
|
||||
return _sky->get_cloud_layer(index)->getMaxAlpha();
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_maxalpha (int index, double maxalpha)
|
||||
{
|
||||
_sky->get_cloud_layer(index)->setMaxAlpha(maxalpha);
|
||||
}
|
||||
|
||||
void
|
||||
FGEnvironmentMgr::set_cloud_layer_coverage_type (int index, int type )
|
||||
{
|
||||
if( type < 0 || type >= SGCloudLayer::SG_MAX_CLOUD_COVERAGES ) {
|
||||
SG_LOG(SG_ENVIRONMENT,SG_WARN,"Unknown cloud layer type " << type << " ignored" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( static_cast<SGCloudLayer::Coverage>(type) == _sky->get_cloud_layer(index)->getCoverage() )
|
||||
return;
|
||||
|
||||
_sky->get_cloud_layer(index)->setCoverage(static_cast<SGCloudLayer::Coverage>(type));
|
||||
_cloudLayersDirty = true;
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGEnvironmentMgr> registrantFGEnvironmentMgr;
|
||||
|
||||
// end of environment-mgr.cxx
|
||||
115
src/Environment/environment_mgr.hxx
Normal file
115
src/Environment/environment_mgr.hxx
Normal file
@@ -0,0 +1,115 @@
|
||||
// environment-mgr.hxx -- manager for natural environment information.
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef _ENVIRONMENT_MGR_HXX
|
||||
#define _ENVIRONMENT_MGR_HXX
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
class FGEnvironment;
|
||||
class FGClimate;
|
||||
class FGClouds;
|
||||
class FGPrecipitationMgr;
|
||||
class SGSky;
|
||||
struct FGEnvironmentMgrMultiplayerListener;
|
||||
|
||||
/**
|
||||
* Manage environment information.
|
||||
*/
|
||||
class FGEnvironmentMgr : public SGSubsystemGroup
|
||||
{
|
||||
public:
|
||||
enum {
|
||||
MAX_CLOUD_LAYERS = 5
|
||||
};
|
||||
|
||||
FGEnvironmentMgr ();
|
||||
virtual ~FGEnvironmentMgr ();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
InitStatus incrementalInit() override;
|
||||
void reinit() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "environment"; }
|
||||
|
||||
/**
|
||||
* Get the environment information for the plane's current position.
|
||||
*/
|
||||
virtual FGEnvironment getEnvironment () const;
|
||||
|
||||
const FGEnvironment* getAircraftEnvironment() const;
|
||||
|
||||
virtual FGEnvironment getEnvironmentAtPosition(const SGGeod& aPos) const;
|
||||
|
||||
private:
|
||||
friend FGEnvironmentMgrMultiplayerListener;
|
||||
void updateClosestAirport();
|
||||
void updateTowerPosition();
|
||||
|
||||
double get_cloud_layer_span_m (int index) const;
|
||||
void set_cloud_layer_span_m (int index, double span_m);
|
||||
double get_cloud_layer_elevation_ft (int index) const;
|
||||
void set_cloud_layer_elevation_ft (int index, double elevation_ft);
|
||||
double get_cloud_layer_thickness_ft (int index) const;
|
||||
void set_cloud_layer_thickness_ft (int index, double thickness_ft);
|
||||
double get_cloud_layer_transition_ft (int index) const;
|
||||
void set_cloud_layer_transition_ft (int index, double transition_ft);
|
||||
const char * get_cloud_layer_coverage (int index) const;
|
||||
void set_cloud_layer_coverage (int index, const char * coverage);
|
||||
int get_cloud_layer_coverage_type (int index) const;
|
||||
void set_cloud_layer_coverage_type (int index, int type );
|
||||
double get_cloud_layer_visibility_m (int index) const;
|
||||
void set_cloud_layer_visibility_m (int index, double visibility_m);
|
||||
double get_cloud_layer_maxalpha (int index ) const;
|
||||
void set_cloud_layer_maxalpha (int index, double maxalpha);
|
||||
|
||||
FGClimate * _climate = nullptr;
|
||||
FGEnvironment * _environment = nullptr; // always the same, for now
|
||||
FGClouds *fgClouds = nullptr;
|
||||
bool _cloudLayersDirty = true;
|
||||
int max_tower_height_feet;
|
||||
int min_tower_height_feet;
|
||||
int default_tower_height_feet;
|
||||
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
SGPropertyChangeListener * _3dCloudsEnableListener;
|
||||
FGEnvironmentMgrMultiplayerListener * _multiplayerListener;
|
||||
SGSky* _sky;
|
||||
|
||||
SGPropertyNode_ptr towerViewPositionLatDegNode;
|
||||
SGPropertyNode_ptr towerViewPositionLonDegNode;
|
||||
SGPropertyNode_ptr towerViewPositionAltFtNode;
|
||||
|
||||
const class FGAICarrier* nearestCarrier;
|
||||
const class FGAirport* nearestAirport;
|
||||
};
|
||||
|
||||
#endif // _ENVIRONMENT_MGR_HXX
|
||||
111
src/Environment/ephemeris.cxx
Normal file
111
src/Environment/ephemeris.cxx
Normal file
@@ -0,0 +1,111 @@
|
||||
// ephemeris.cxx -- wrap SGEphemeris code in a subsystem
|
||||
//
|
||||
// Written by James Turner, started June 2010.
|
||||
//
|
||||
// Copyright (C) 2010 Curtis L. Olson - http://www.flightgear.org/~curt
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#include <Environment/ephemeris.hxx>
|
||||
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <simgear/ephemeris/ephemeris.hxx>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
|
||||
static void tieStar(const char* prop, Star* s, double (Star::*getter)() const)
|
||||
{
|
||||
fgGetNode(prop, true)->tie(SGRawValueMethods<Star, double>(*s, getter, NULL));
|
||||
}
|
||||
|
||||
static void tieMoonPos(const char* prop, MoonPos* s, double (MoonPos::*getter)() const)
|
||||
{
|
||||
fgGetNode(prop, true)->tie(SGRawValueMethods<MoonPos, double>(*s, getter, NULL));
|
||||
}
|
||||
|
||||
Ephemeris::Ephemeris()
|
||||
{
|
||||
}
|
||||
|
||||
Ephemeris::~Ephemeris()
|
||||
{
|
||||
}
|
||||
|
||||
SGEphemeris* Ephemeris::data()
|
||||
{
|
||||
return _impl.get();
|
||||
}
|
||||
|
||||
void Ephemeris::init()
|
||||
{
|
||||
SGPath ephem_data_path(globals->get_fg_root());
|
||||
ephem_data_path.append("Astro");
|
||||
_impl.reset(new SGEphemeris(ephem_data_path));
|
||||
|
||||
tieStar("/ephemeris/sun/xs", _impl->get_sun(), &Star::getxs);
|
||||
tieStar("/ephemeris/sun/ys", _impl->get_sun(), &Star::getys);
|
||||
tieStar("/ephemeris/sun/ze", _impl->get_sun(), &Star::getze);
|
||||
tieStar("/ephemeris/sun/ye", _impl->get_sun(), &Star::getye);
|
||||
tieStar("/ephemeris/sun/lat-deg", _impl->get_sun(), &Star::getLat);
|
||||
|
||||
tieMoonPos("/ephemeris/moon/xg", _impl->get_moon(), &MoonPos::getxg);
|
||||
tieMoonPos("/ephemeris/moon/yg", _impl->get_moon(), &MoonPos::getyg);
|
||||
tieMoonPos("/ephemeris/moon/ze", _impl->get_moon(), &MoonPos::getze);
|
||||
tieMoonPos("/ephemeris/moon/ye", _impl->get_moon(), &MoonPos::getye);
|
||||
tieMoonPos("/ephemeris/moon/lat-deg", _impl->get_moon(), &MoonPos::getLat);
|
||||
tieMoonPos("/ephemeris/moon/age", _impl->get_moon(), &MoonPos::getAge);
|
||||
tieMoonPos("/ephemeris/moon/phase", _impl->get_moon(), &MoonPos::getPhase);
|
||||
|
||||
_latProp = fgGetNode("/position/latitude-deg", true);
|
||||
|
||||
_moonlight = fgGetNode("/environment/moonlight", true);
|
||||
|
||||
update(0.0);
|
||||
}
|
||||
|
||||
void Ephemeris::shutdown()
|
||||
{
|
||||
_impl.reset();
|
||||
}
|
||||
|
||||
void Ephemeris::postinit()
|
||||
{
|
||||
}
|
||||
|
||||
void Ephemeris::bind()
|
||||
{
|
||||
}
|
||||
|
||||
void Ephemeris::unbind()
|
||||
{
|
||||
_latProp = 0;
|
||||
_latProp.reset();
|
||||
_moonlight.reset();
|
||||
}
|
||||
|
||||
void Ephemeris::update(double)
|
||||
{
|
||||
SGTime* st = globals->get_time_params();
|
||||
_impl->update(st->getMjd(), st->getLst(), _latProp->getDoubleValue());
|
||||
|
||||
// Update the moonlight intensity.
|
||||
_moonlight->setDoubleValue(_impl->get_moon()->getIlluminanceFactor());
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<Ephemeris> registrantEphemeris;
|
||||
61
src/Environment/ephemeris.hxx
Normal file
61
src/Environment/ephemeris.hxx
Normal file
@@ -0,0 +1,61 @@
|
||||
// ephemeris.hxx -- wrap SGEphemeris code in a subsystem
|
||||
//
|
||||
// Written by James Turner, started June 2010.
|
||||
//
|
||||
// Copyright (C) 2010 Curtis L. Olson - http://www.flightgear.org/~curt
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#ifndef FG_ENVIRONMENT_EPHEMERIS_HXX
|
||||
#define FG_ENVIRONMENT_EPHEMERIS_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
class SGEphemeris;
|
||||
class SGPropertyNode;
|
||||
|
||||
/**
|
||||
* Wrap SGEphemeris in a subsystem/property interface
|
||||
*/
|
||||
class Ephemeris : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
Ephemeris();
|
||||
~Ephemeris();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void postinit() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "ephemeris"; }
|
||||
|
||||
SGEphemeris* data();
|
||||
|
||||
private:
|
||||
std::unique_ptr<SGEphemeris> _impl;
|
||||
SGPropertyNode_ptr _latProp;
|
||||
SGPropertyNode_ptr _moonlight;
|
||||
};
|
||||
|
||||
#endif // of FG_ENVIRONMENT_EPHEMERIS_HXX
|
||||
417
src/Environment/fgclouds.cxx
Normal file
417
src/Environment/fgclouds.cxx
Normal file
@@ -0,0 +1,417 @@
|
||||
// Build a cloud layer based on metar
|
||||
//
|
||||
// Written by Harald JOHNSEN, started April 2005.
|
||||
//
|
||||
// Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "fgclouds.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/sound/soundmgr.hxx>
|
||||
#include <simgear/scene/sky/sky.hxx>
|
||||
//#include <simgear/environment/visual_enviro.hxx>
|
||||
#include <simgear/scene/sky/cloudfield.hxx>
|
||||
#include <simgear/scene/sky/newcloud.hxx>
|
||||
#include <simgear/structure/commands.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/util.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Airports/airport.hxx>
|
||||
|
||||
// RNG seed to ensure cloud synchronization across multi-process
|
||||
// deployments
|
||||
static mt seed;
|
||||
|
||||
FGClouds::FGClouds() :
|
||||
clouds_3d_enabled(false),
|
||||
index(0)
|
||||
{
|
||||
update_event = 0;
|
||||
}
|
||||
|
||||
FGClouds::~FGClouds()
|
||||
{
|
||||
globals->get_commands()->removeCommand("add-cloud");
|
||||
globals->get_commands()->removeCommand("del-cloud");
|
||||
globals->get_commands()->removeCommand("move-cloud");
|
||||
|
||||
}
|
||||
|
||||
int FGClouds::get_update_event(void) const {
|
||||
return update_event;
|
||||
}
|
||||
|
||||
void FGClouds::set_update_event(int count) {
|
||||
update_event = count;
|
||||
buildCloudLayers();
|
||||
}
|
||||
|
||||
void FGClouds::Init(void)
|
||||
{
|
||||
mt_init_time_10(&seed);
|
||||
|
||||
globals->get_commands()->addCommand("add-cloud", this, &FGClouds::add3DCloud);
|
||||
globals->get_commands()->addCommand("del-cloud", this, &FGClouds::delete3DCloud);
|
||||
globals->get_commands()->addCommand("move-cloud", this, &FGClouds::move3DCloud);
|
||||
}
|
||||
|
||||
// Build an invidual cloud. Returns the extents of the cloud for coverage calculations
|
||||
double FGClouds::buildCloud(SGPropertyNode *cloud_def_root, SGPropertyNode *box_def_root,
|
||||
const std::string& name, double grid_z_rand, SGCloudField *layer)
|
||||
{
|
||||
SGPropertyNode *box_def=NULL;
|
||||
SGPropertyNode *cld_def=NULL;
|
||||
double extent = 0.0;
|
||||
|
||||
SGPath texture_root = globals->get_fg_root();
|
||||
texture_root.append("Textures");
|
||||
texture_root.append("Sky");
|
||||
|
||||
box_def = box_def_root->getChild(name.c_str());
|
||||
|
||||
string base_name = name.substr(0,2);
|
||||
if( !box_def ) {
|
||||
if( name[2] == '-' ) {
|
||||
box_def = box_def_root->getChild(base_name.c_str());
|
||||
}
|
||||
if( !box_def )
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double x = mt_rand(&seed) * SGCloudField::fieldSize - (SGCloudField::fieldSize / 2.0);
|
||||
double y = mt_rand(&seed) * SGCloudField::fieldSize - (SGCloudField::fieldSize / 2.0);
|
||||
double z = grid_z_rand * (mt_rand(&seed) - 0.5);
|
||||
|
||||
float lon = fgGetNode("/position/longitude-deg", false)->getFloatValue();
|
||||
float lat = fgGetNode("/position/latitude-deg", false)->getFloatValue();
|
||||
|
||||
SGVec3f pos(x,y,z);
|
||||
|
||||
for(int i = 0; i < box_def->nChildren() ; i++) {
|
||||
SGPropertyNode *abox = box_def->getChild(i);
|
||||
if( abox->getNameString() == "box" ) {
|
||||
|
||||
string type = abox->getStringValue("type", "cu-small");
|
||||
cld_def = cloud_def_root->getChild(type.c_str());
|
||||
if ( !cld_def ) return 0.0;
|
||||
|
||||
double w = abox->getDoubleValue("width", 1000.0);
|
||||
double h = abox->getDoubleValue("height", 1000.0);
|
||||
int hdist = abox->getIntValue("hdist", 1);
|
||||
int vdist = abox->getIntValue("vdist", 1);
|
||||
|
||||
double c = abox->getDoubleValue("count", 5);
|
||||
int count = (int) (c + (mt_rand(&seed) - 0.5) * c);
|
||||
|
||||
extent = std::max(w*w, extent);
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
|
||||
// Locate the clouds randomly in the defined space. The hdist and
|
||||
// vdist values control the horizontal and vertical distribution
|
||||
// by simply summing random components.
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
double z = 0.0;
|
||||
|
||||
for (int k = 0; k < hdist; k++)
|
||||
{
|
||||
x += (mt_rand(&seed) / hdist);
|
||||
y += (mt_rand(&seed) / hdist);
|
||||
}
|
||||
|
||||
for (int k = 0; k < vdist; k++)
|
||||
{
|
||||
z += (mt_rand(&seed) / vdist);
|
||||
}
|
||||
|
||||
x = w * (x - 0.5) + pos[0]; // N/S
|
||||
y = w * (y - 0.5) + pos[1]; // E/W
|
||||
z = h * z + pos[2]; // Up/Down. pos[2] is the cloudbase
|
||||
|
||||
//SGVec3f newpos = SGVec3f(x, y, z);
|
||||
SGNewCloud cld(texture_root, cld_def, &seed);
|
||||
|
||||
//layer->addCloud(newpos, cld.genCloud());
|
||||
layer->addCloud(lon, lat, z, x, y, index++, cld.genCloud());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the maximum extent of the cloud
|
||||
return extent;
|
||||
}
|
||||
|
||||
void FGClouds::buildLayer(int iLayer, const string& name, double coverage) {
|
||||
struct {
|
||||
string name;
|
||||
double count;
|
||||
} tCloudVariety[20];
|
||||
int CloudVarietyCount = 0;
|
||||
double totalCount = 0.0;
|
||||
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
|
||||
SGPropertyNode *cloud_def_root = fgGetNode("/environment/cloudlayers/clouds", false);
|
||||
SGPropertyNode *box_def_root = fgGetNode("/environment/cloudlayers/boxes", false);
|
||||
SGPropertyNode *layer_def_root = fgGetNode("/environment/cloudlayers/layers", false);
|
||||
SGCloudField *layer = thesky->get_cloud_layer(iLayer)->get_layer3D();
|
||||
layer->clear();
|
||||
|
||||
// If we don't have the required properties, then render the cloud in 2D
|
||||
if ((! clouds_3d_enabled) || coverage == 0.0 ||
|
||||
layer_def_root == NULL || cloud_def_root == NULL || box_def_root == NULL) {
|
||||
thesky->get_cloud_layer(iLayer)->set_enable3dClouds(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can't find a definition for this cloud type, then render the cloud in 2D
|
||||
SGPropertyNode *layer_def=NULL;
|
||||
layer_def = layer_def_root->getChild(name.c_str());
|
||||
if( !layer_def ) {
|
||||
if( name[2] == '-' ) {
|
||||
string base_name = name.substr(0,2);
|
||||
layer_def = layer_def_root->getChild(base_name.c_str());
|
||||
}
|
||||
if( !layer_def ) {
|
||||
thesky->get_cloud_layer(iLayer)->set_enable3dClouds(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we know we've got some 3D clouds to generate.
|
||||
thesky->get_cloud_layer(iLayer)->set_enable3dClouds(true);
|
||||
|
||||
double grid_z_rand = layer_def->getDoubleValue("grid-z-rand");
|
||||
|
||||
for(int i = 0; i < layer_def->nChildren() ; i++) {
|
||||
SGPropertyNode *acloud = layer_def->getChild(i);
|
||||
if( acloud->getNameString() == "cloud" ) {
|
||||
string cloud_name = acloud->getStringValue("name");
|
||||
tCloudVariety[CloudVarietyCount].name = cloud_name;
|
||||
double count = acloud->getDoubleValue("count", 1.0);
|
||||
tCloudVariety[CloudVarietyCount].count = count;
|
||||
int variety = 0;
|
||||
char variety_name[50];
|
||||
do {
|
||||
variety++;
|
||||
snprintf(variety_name, sizeof(variety_name) - 1, "%s-%d", cloud_name.c_str(), variety);
|
||||
} while( box_def_root->getChild(variety_name, 0, false) );
|
||||
|
||||
totalCount += count;
|
||||
if( CloudVarietyCount < 20 )
|
||||
CloudVarietyCount++;
|
||||
}
|
||||
}
|
||||
totalCount = 1.0 / totalCount;
|
||||
|
||||
// Determine how much cloud coverage we need in m^2.
|
||||
double cov = coverage * SGCloudField::fieldSize * SGCloudField::fieldSize;
|
||||
|
||||
while (cov > 0.0f) {
|
||||
double choice = mt_rand(&seed);
|
||||
|
||||
for(int i = 0; i < CloudVarietyCount ; i ++) {
|
||||
choice -= tCloudVariety[i].count * totalCount;
|
||||
if( choice <= 0.0 ) {
|
||||
cov -= buildCloud(cloud_def_root,
|
||||
box_def_root,
|
||||
tCloudVariety[i].name,
|
||||
grid_z_rand,
|
||||
layer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we've built any clouds, enable them and set the density (coverage)
|
||||
//layer->setCoverage(coverage);
|
||||
//layer->applyCoverage();
|
||||
thesky->get_cloud_layer(iLayer)->set_enable3dClouds(clouds_3d_enabled);
|
||||
}
|
||||
|
||||
void FGClouds::buildCloudLayers(void) {
|
||||
SGPropertyNode *metar_root = fgGetNode("/environment", true);
|
||||
|
||||
//double wind_speed_kt = metar_root->getDoubleValue("wind-speed-kt");
|
||||
double temperature_degc = metar_root->getDoubleValue("temperature-sea-level-degc");
|
||||
double dewpoint_degc = metar_root->getDoubleValue("dewpoint-sea-level-degc");
|
||||
double pressure_mb = metar_root->getDoubleValue("pressure-sea-level-inhg") * SG_INHG_TO_PA / 100.0;
|
||||
double rel_humidity = metar_root->getDoubleValue("relative-humidity");
|
||||
|
||||
// formule d'Epsy, base d'un cumulus
|
||||
double cumulus_base = 122.0 * (temperature_degc - dewpoint_degc);
|
||||
double stratus_base = 100.0 * (100.0 - rel_humidity) * SG_FEET_TO_METER;
|
||||
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
|
||||
SGPropertyNode *cloud_root = fgGetNode("/environment/clouds/layer", iLayer, true);
|
||||
|
||||
double alt_ft = cloud_root->getDoubleValue("elevation-ft");
|
||||
double alt_m = alt_ft * SG_FEET_TO_METER;
|
||||
string coverage = cloud_root->getStringValue("coverage");
|
||||
|
||||
double coverage_norm = 0.0;
|
||||
if( coverage == "few" )
|
||||
coverage_norm = 2.0/8.0; // <1-2
|
||||
else if( coverage == "scattered" )
|
||||
coverage_norm = 4.0/8.0; // 3-4
|
||||
else if( coverage == "broken" )
|
||||
coverage_norm = 6.0/8.0; // 5-7
|
||||
else if( coverage == "overcast" )
|
||||
coverage_norm = 8.0/8.0; // 8
|
||||
|
||||
string layer_type = "nn";
|
||||
|
||||
if( coverage == "cirrus" ) {
|
||||
layer_type = "ci";
|
||||
} else if( alt_ft > 16500 ) {
|
||||
// layer_type = "ci|cs|cc";
|
||||
layer_type = "ci";
|
||||
} else if( alt_ft > 6500 ) {
|
||||
// layer_type = "as|ac|ns";
|
||||
layer_type = "ac";
|
||||
if( pressure_mb < 1005.0 && coverage_norm >= 0.5 )
|
||||
layer_type = "ns";
|
||||
} else {
|
||||
// layer_type = "st|cu|cb|sc";
|
||||
if( cumulus_base * 0.80 < alt_m && cumulus_base * 1.20 > alt_m ) {
|
||||
// +/- 20% from cumulus probable base
|
||||
layer_type = "cu";
|
||||
} else if( stratus_base * 0.80 < alt_m && stratus_base * 1.40 > alt_m ) {
|
||||
// +/- 20% from stratus probable base
|
||||
layer_type = "st";
|
||||
} else {
|
||||
// above formulae is far from perfect
|
||||
if ( alt_ft < 2000 )
|
||||
layer_type = "st";
|
||||
else if( alt_ft < 4500 )
|
||||
layer_type = "cu";
|
||||
else
|
||||
layer_type = "sc";
|
||||
}
|
||||
}
|
||||
|
||||
cloud_root->setStringValue("layer-type",layer_type);
|
||||
buildLayer(iLayer, layer_type, coverage_norm);
|
||||
}
|
||||
}
|
||||
|
||||
void FGClouds::set_3dClouds(bool enable)
|
||||
{
|
||||
if (enable != clouds_3d_enabled) {
|
||||
clouds_3d_enabled = enable;
|
||||
buildCloudLayers();
|
||||
}
|
||||
}
|
||||
|
||||
bool FGClouds::get_3dClouds() const
|
||||
{
|
||||
return clouds_3d_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a 3D cloud to a cloud layer.
|
||||
*
|
||||
* Property arguments
|
||||
* layer - the layer index to add this cloud to. (Defaults to 0)
|
||||
* index - the index for this cloud (to be used later)
|
||||
* lon/lat/alt - the position for the cloud
|
||||
* (Various) - cloud definition properties. See README.3DClouds
|
||||
*
|
||||
*/
|
||||
bool FGClouds::add3DCloud(const SGPropertyNode *arg, SGPropertyNode * root)
|
||||
{
|
||||
int l = arg->getIntValue("layer", 0);
|
||||
int index = arg->getIntValue("index", 0);
|
||||
|
||||
SGPath texture_root = globals->get_fg_root();
|
||||
texture_root.append("Textures");
|
||||
texture_root.append("Sky");
|
||||
|
||||
float lon = arg->getFloatValue("lon-deg", 0.0f);
|
||||
float lat = arg->getFloatValue("lat-deg", 0.0f);
|
||||
float alt = arg->getFloatValue("alt-ft", 0.0f);
|
||||
float x = arg->getFloatValue("x-offset-m", 0.0f);
|
||||
float y = arg->getFloatValue("y-offset-m", 0.0f);
|
||||
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
SGCloudField *layer = thesky->get_cloud_layer(l)->get_layer3D();
|
||||
SGNewCloud cld(texture_root, arg, &seed);
|
||||
bool success = layer->addCloud(lon, lat, alt, x, y, index, cld.genCloud());
|
||||
|
||||
// Adding a 3D cloud immediately makes this layer 3D.
|
||||
thesky->get_cloud_layer(l)->set_enable3dClouds(true);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a 3D cloud from a cloud layer
|
||||
*
|
||||
* Property arguments
|
||||
*
|
||||
* layer - the layer index to remove this cloud from. (defaults to 0)
|
||||
* index - the cloud index
|
||||
*
|
||||
*/
|
||||
bool FGClouds::delete3DCloud(const SGPropertyNode *arg, SGPropertyNode * root)
|
||||
{
|
||||
int l = arg->getIntValue("layer", 0);
|
||||
int i = arg->getIntValue("index", 0);
|
||||
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
SGCloudField *layer = thesky->get_cloud_layer(l)->get_layer3D();
|
||||
return layer->deleteCloud(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a cloud within a 3D layer
|
||||
*
|
||||
* Property arguments
|
||||
* layer - the layer index to add this cloud to. (Defaults to 0)
|
||||
* index - the cloud index to move.
|
||||
* lon/lat/alt - the position for the cloud
|
||||
*
|
||||
*/
|
||||
bool FGClouds::move3DCloud(const SGPropertyNode *arg, SGPropertyNode * root)
|
||||
{
|
||||
int l = arg->getIntValue("layer", 0);
|
||||
int i = arg->getIntValue("index", 0);
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
|
||||
float lon = arg->getFloatValue("lon-deg", 0.0f);
|
||||
float lat = arg->getFloatValue("lat-deg", 0.0f);
|
||||
float alt = arg->getFloatValue("alt-ft", 0.0f);
|
||||
float x = arg->getFloatValue("x-offset-m", 0.0f);
|
||||
float y = arg->getFloatValue("y-offset-m", 0.0f);
|
||||
|
||||
SGCloudField *layer = thesky->get_cloud_layer(l)->get_layer3D();
|
||||
return layer->repositionCloud(i, lon, lat, alt, x, y);
|
||||
}
|
||||
63
src/Environment/fgclouds.hxx
Normal file
63
src/Environment/fgclouds.hxx
Normal file
@@ -0,0 +1,63 @@
|
||||
// Build a cloud layer based on metar
|
||||
//
|
||||
// Written by Harald JOHNSEN, started April 2005.
|
||||
//
|
||||
// Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
#ifndef _FGCLOUDS_HXX
|
||||
#define _FGCLOUDS_HXX
|
||||
|
||||
#include <string>
|
||||
|
||||
// forward decls
|
||||
class SGPropertyNode;
|
||||
class SGCloudField;
|
||||
|
||||
class FGClouds {
|
||||
|
||||
private:
|
||||
double buildCloud(SGPropertyNode *cloud_def_root, SGPropertyNode *box_def_root,
|
||||
const std::string& name, double grid_z_rand, SGCloudField *layer);
|
||||
void buildLayer(int iLayer, const std::string& name, double coverage);
|
||||
|
||||
void buildCloudLayers(void);
|
||||
|
||||
int update_event;
|
||||
|
||||
bool clouds_3d_enabled;
|
||||
int index;
|
||||
|
||||
bool add3DCloud(const SGPropertyNode *arg, SGPropertyNode * root);
|
||||
bool delete3DCloud(const SGPropertyNode *arg, SGPropertyNode * root);
|
||||
bool move3DCloud(const SGPropertyNode *arg, SGPropertyNode * root);
|
||||
|
||||
public:
|
||||
FGClouds();
|
||||
~FGClouds();
|
||||
|
||||
void Init(void);
|
||||
|
||||
int get_update_event(void) const;
|
||||
void set_update_event(int count);
|
||||
bool get_3dClouds() const;
|
||||
void set_3dClouds(bool enable);
|
||||
|
||||
};
|
||||
|
||||
#endif // _FGCLOUDS_HXX
|
||||
|
||||
167
src/Environment/fgmetar.cxx
Normal file
167
src/Environment/fgmetar.cxx
Normal file
@@ -0,0 +1,167 @@
|
||||
// metar interface class
|
||||
//
|
||||
// Written by Melchior FRANZ, started January 2005.
|
||||
//
|
||||
// Copyright (C) 2005 Melchior FRANZ - mfranz@aon.at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
/**
|
||||
* @file fgmetar.cxx
|
||||
* Implements FGMetar class that inherits from SGMetar.
|
||||
*
|
||||
* o provide defaults for unset values
|
||||
* o interpolate/randomize data (GREATER_THAN)
|
||||
* o derive additional values (time, age, snow cover)
|
||||
* o consider minimum identifier (CAVOK, mil. color codes)
|
||||
*
|
||||
* TODO
|
||||
* - NSC & mil. color codes
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <simgear/math/sg_random.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <simgear/timing/lowleveltime.h>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
#include "fgmetar.hxx"
|
||||
|
||||
const double CAVOK_VISIBILITY = 9999.0;
|
||||
|
||||
FGMetar::FGMetar(const string& icao) :
|
||||
SGMetar(icao),
|
||||
_snow_cover(false)
|
||||
{
|
||||
int i;
|
||||
double d;
|
||||
|
||||
// CAVOK: visibility >= 10km; lowest cloud layer >= 5000 ft; any coverage
|
||||
if (getCAVOK()) {
|
||||
if (_min_visibility.getVisibility_m() == SGMetarNaN)
|
||||
_min_visibility.set(CAVOK_VISIBILITY);
|
||||
|
||||
if (_max_visibility.getVisibility_m() == SGMetarNaN)
|
||||
_min_visibility.set(CAVOK_VISIBILITY);
|
||||
|
||||
vector<SGMetarCloud> cv = _clouds;;
|
||||
if (cv.empty()) {
|
||||
SGMetarCloud cl;
|
||||
cl.set(5500 * SG_FEET_TO_METER, SGMetarCloud::COVERAGE_SCATTERED);
|
||||
_clouds.push_back(cl);
|
||||
}
|
||||
}
|
||||
|
||||
// visibility
|
||||
d = _min_visibility.getVisibility_m();
|
||||
if (d == SGMetarNaN)
|
||||
d = 10000.0;
|
||||
if (_min_visibility.getModifier() == SGMetarVisibility::GREATER_THAN)
|
||||
d += 15000.0;// * sg_random();
|
||||
_min_visibility.set(d);
|
||||
|
||||
if (_max_visibility.getVisibility_m() == SGMetarNaN)
|
||||
_max_visibility.set(d);
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
d = _dir_visibility[i].getVisibility_m();
|
||||
if (d == SGMetarNaN)
|
||||
_dir_visibility[i].set(10000.0);
|
||||
if (_dir_visibility[i].getModifier() == SGMetarVisibility::GREATER_THAN)
|
||||
d += 15000.0;// * sg_random();
|
||||
_dir_visibility[i].set(d);
|
||||
}
|
||||
|
||||
// wind
|
||||
if (_wind_dir == -1) {
|
||||
if (_wind_range_from == -1) {
|
||||
_wind_dir = 0;
|
||||
_wind_range_from = 0;
|
||||
_wind_range_to = 359;
|
||||
} else {
|
||||
_wind_dir = (_wind_range_from + _wind_range_to) / 2;
|
||||
}
|
||||
} else if (_wind_range_from == -1) {
|
||||
_wind_range_from = _wind_range_to = _wind_dir;
|
||||
}
|
||||
|
||||
if (_wind_speed == SGMetarNaN)
|
||||
_wind_speed = 0.0;
|
||||
if (_gust_speed == SGMetarNaN)
|
||||
_gust_speed = 0.0;
|
||||
|
||||
// clouds
|
||||
vector<SGMetarCloud> cv = _clouds;
|
||||
vector<SGMetarCloud>::iterator cloud, cv_end = cv.end();
|
||||
|
||||
for (i = 0, cloud = cv.begin(); cloud != cv_end; ++cloud, i++) {
|
||||
SGMetarCloud::Coverage cov = cloud->getCoverage();
|
||||
if (cov == SGMetarCloud::COVERAGE_NIL)
|
||||
cov = SGMetarCloud::COVERAGE_CLEAR;
|
||||
|
||||
double alt = cloud->getAltitude_ft();
|
||||
if (alt == SGMetarNaN)
|
||||
alt = -9999;
|
||||
|
||||
cloud->set(alt, cov);
|
||||
}
|
||||
|
||||
|
||||
// temperature/pressure
|
||||
if (_temp == SGMetarNaN)
|
||||
_temp = 15.0;
|
||||
|
||||
if (_dewp == SGMetarNaN)
|
||||
_dewp = 0.0;
|
||||
|
||||
if (_pressure == SGMetarNaN)
|
||||
_pressure = 30.0 * SG_INHG_TO_PA;
|
||||
|
||||
// snow cover
|
||||
map<string, SGMetarRunway> rm = getRunways();
|
||||
map<string, SGMetarRunway>::const_iterator runway, rm_end = rm.end();
|
||||
for (runway = rm.begin(); runway != rm_end; ++runway) {
|
||||
SGMetarRunway rwy = runway->second;
|
||||
if (rwy.getDeposit() >= 3 ) {
|
||||
_snow_cover = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_temp < 5.0 && _snow)
|
||||
_snow_cover = true;
|
||||
if (_temp < 1.0 && getRelHumidity() > 80)
|
||||
_snow_cover = true;
|
||||
|
||||
_time = sgTimeGetGMT(_year - 1900, _month - 1, _day, _hour, _minute, 0);
|
||||
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "FGMetar:" << getDataString());
|
||||
if (_x_proxy)
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "METAR from proxy");
|
||||
else
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "METAR from tgftp.nws.noaa.gov");
|
||||
}
|
||||
|
||||
|
||||
long FGMetar::getAge_min() const
|
||||
{
|
||||
time_t now = _x_proxy ? _rq_time : time(nullptr);
|
||||
return (now - _time) / 60;
|
||||
}
|
||||
|
||||
48
src/Environment/fgmetar.hxx
Normal file
48
src/Environment/fgmetar.hxx
Normal file
@@ -0,0 +1,48 @@
|
||||
// metar interface class
|
||||
//
|
||||
// Written by Melchior FRANZ, started January 2005.
|
||||
//
|
||||
// Copyright (C) 2005 Melchior FRANZ - mfranz@aon.at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
|
||||
#include <simgear/environment/metar.hxx>
|
||||
|
||||
|
||||
class FGMetar : public SGMetar, public SGReferenced {
|
||||
public:
|
||||
FGMetar(const std::string& icao);
|
||||
|
||||
long getAge_min() const;
|
||||
time_t getTime() const { return _time; }
|
||||
double getRain() const { return _rain / 3.0; }
|
||||
double getHail() const { return _hail / 3.0; }
|
||||
double getSnow() const { return _snow / 3.0; }
|
||||
bool getSnowCover() const { return _snow_cover; }
|
||||
|
||||
private:
|
||||
time_t _rq_time;
|
||||
time_t _time;
|
||||
bool _snow_cover;
|
||||
};
|
||||
88
src/Environment/gravity.cxx
Normal file
88
src/Environment/gravity.cxx
Normal file
@@ -0,0 +1,88 @@
|
||||
// gravity.cxx -- interface for earth gravitational model
|
||||
//
|
||||
// Written by Torsten Dreyer, June 2011
|
||||
//
|
||||
// Copyright (C) 2011 Torsten Dreyer - torsten (at) t3r _dot_ de
|
||||
//
|
||||
// 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 "gravity.hxx"
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
/*
|
||||
http://de.wikipedia.org/wiki/Normalschwereformel
|
||||
*/
|
||||
class Somigliana : public Gravity {
|
||||
public:
|
||||
Somigliana();
|
||||
virtual ~Somigliana();
|
||||
virtual double getGravity( const SGGeod & position ) const;
|
||||
};
|
||||
|
||||
Somigliana::Somigliana()
|
||||
{
|
||||
}
|
||||
|
||||
Somigliana::~Somigliana()
|
||||
{
|
||||
}
|
||||
|
||||
double Somigliana::getGravity( const SGGeod & position ) const
|
||||
{
|
||||
// Geodetic Reference System 1980 parameter
|
||||
#define A 6378137.0 // equatorial radius of earth
|
||||
#define B 6356752.3141 // semiminor axis
|
||||
#define AGA (A*9.7803267715) // A times normal gravity at equator
|
||||
#define BGB (B*9.8321863685) // B times normal gravity at pole
|
||||
// forumla of Somigliana
|
||||
double cosphi = ::cos(position.getLatitudeRad());
|
||||
double cos2phi = cosphi*cosphi;
|
||||
double sinphi = ::sin(position.getLatitudeRad());
|
||||
double sin2phi = sinphi*sinphi;
|
||||
double g0 = (AGA * cos2phi + BGB * sin2phi) / sqrt( A*A*cos2phi+B*B*sin2phi );
|
||||
|
||||
static const double k1 = 3.15704e-7;
|
||||
static const double k2 = 2.10269e-9;
|
||||
static const double k3 = 7.37452e-14;
|
||||
|
||||
double h = position.getElevationM();
|
||||
|
||||
return g0*(1-(k1-k2*sin2phi)*h+k3*h*h);
|
||||
}
|
||||
|
||||
static Somigliana _somigliana;
|
||||
|
||||
/* --------------------- Gravity implementation --------------------- */
|
||||
Gravity * Gravity::_instance = NULL;
|
||||
|
||||
Gravity::~Gravity()
|
||||
{
|
||||
}
|
||||
|
||||
//double Gravity::getGravity( const SGGeoc & position ) = 0;
|
||||
|
||||
const Gravity * Gravity::instance()
|
||||
{
|
||||
if( _instance == NULL )
|
||||
_instance = &_somigliana;
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
43
src/Environment/gravity.hxx
Normal file
43
src/Environment/gravity.hxx
Normal file
@@ -0,0 +1,43 @@
|
||||
// gravity.hxx -- interface for earth gravitational model
|
||||
//
|
||||
// Written by Torsten Dreyer, June 2011
|
||||
//
|
||||
// Copyright (C) 2011 Torsten Dreyer - torsten (at) t3r _dot_ de
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef __GRAVITY_HXX
|
||||
#define __GRAVITY_HXX
|
||||
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
class Gravity
|
||||
{
|
||||
public:
|
||||
virtual ~Gravity();
|
||||
virtual double getGravity( const SGGeod & position ) const = 0;
|
||||
|
||||
const static Gravity * instance();
|
||||
|
||||
private:
|
||||
static Gravity * _instance;
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif // __GRAVITY_HXX
|
||||
75
src/Environment/magvarmanager.cxx
Normal file
75
src/Environment/magvarmanager.cxx
Normal file
@@ -0,0 +1,75 @@
|
||||
// magvarmanager.cxx -- Wraps the SimGear SGMagVar in a subsystem
|
||||
//
|
||||
// Copyright (C) 2012 James Turner - zakalawe@mac.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "magvarmanager.hxx"
|
||||
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/magvar/magvar.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
FGMagVarManager::FGMagVarManager() :
|
||||
_magVar(new SGMagVar)
|
||||
{
|
||||
}
|
||||
|
||||
FGMagVarManager::~FGMagVarManager()
|
||||
{
|
||||
}
|
||||
|
||||
void FGMagVarManager::init()
|
||||
{
|
||||
update(0.0); // force an immediate update
|
||||
}
|
||||
|
||||
void FGMagVarManager::bind()
|
||||
{
|
||||
_magVarNode = fgGetNode("/environment/magnetic-variation-deg", true);
|
||||
_magDipNode = fgGetNode("/environment/magnetic-dip-deg", true);
|
||||
}
|
||||
|
||||
void FGMagVarManager::unbind()
|
||||
{
|
||||
_magVarNode = SGPropertyNode_ptr();
|
||||
_magDipNode = SGPropertyNode_ptr();
|
||||
}
|
||||
|
||||
void FGMagVarManager::update(double dt)
|
||||
{
|
||||
SG_UNUSED(dt);
|
||||
|
||||
// update magvar model
|
||||
_magVar->update( globals->get_aircraft_position(),
|
||||
globals->get_time_params()->getJD() );
|
||||
|
||||
_magVarNode->setDoubleValue(_magVar->get_magvar() * SG_RADIANS_TO_DEGREES);
|
||||
_magDipNode->setDoubleValue(_magVar->get_magdip() * SG_RADIANS_TO_DEGREES);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGMagVarManager> registrantFGMagVarManager;
|
||||
52
src/Environment/magvarmanager.hxx
Normal file
52
src/Environment/magvarmanager.hxx
Normal file
@@ -0,0 +1,52 @@
|
||||
// magvarmanager.hxx -- Wraps the SimGear SGMagVar in a subsystem
|
||||
//
|
||||
// Copyright (C) 2012 James Turner - zakalawe@mac.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef FG_MAGVAR_MANAGER
|
||||
#define FG_MAGVAR_MANAGER 1
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/props/propsfwd.hxx>
|
||||
|
||||
// forward decls
|
||||
class SGMagVar;
|
||||
|
||||
class FGMagVarManager : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
FGMagVarManager();
|
||||
virtual ~FGMagVarManager();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "magvar"; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<SGMagVar> _magVar;
|
||||
|
||||
SGPropertyNode_ptr _magVarNode, _magDipNode;
|
||||
};
|
||||
|
||||
#endif // of FG_MAGVAR_MANAGER
|
||||
38
src/Environment/metarairportfilter.cxx
Normal file
38
src/Environment/metarairportfilter.cxx
Normal file
@@ -0,0 +1,38 @@
|
||||
// metarairportfilter.cxx -- Implementation of AirportFilter
|
||||
//
|
||||
// Written by Torsten Dreyer, August 2010
|
||||
//
|
||||
// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "metarairportfilter.hxx"
|
||||
|
||||
namespace Environment {
|
||||
|
||||
MetarAirportFilter * MetarAirportFilter::_instance = NULL;
|
||||
|
||||
MetarAirportFilter * MetarAirportFilter::instance()
|
||||
{
|
||||
return _instance != NULL ? _instance :
|
||||
(_instance = new MetarAirportFilter());
|
||||
}
|
||||
|
||||
} // namespace Environment
|
||||
51
src/Environment/metarairportfilter.hxx
Normal file
51
src/Environment/metarairportfilter.hxx
Normal file
@@ -0,0 +1,51 @@
|
||||
// metarairportfilter.hxx -- Implementation of AirportFilter
|
||||
//
|
||||
// Written by Torsten Dreyer, August 2010
|
||||
//
|
||||
// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef __METARAIRPORTFILTER_HXX
|
||||
#define __METARAIRPORTFILTER_HXX
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
/**
|
||||
* @brief A AirportFilter for selection airports that provide a METAR
|
||||
* Singleton implementation of FGAirport::AirportFilter
|
||||
*/
|
||||
class MetarAirportFilter : public FGAirport::AirportFilter {
|
||||
public:
|
||||
static MetarAirportFilter * instance();
|
||||
protected:
|
||||
MetarAirportFilter() {}
|
||||
virtual bool passAirport(FGAirport* aApt) const {
|
||||
return aApt->getMetar();
|
||||
}
|
||||
|
||||
// permit heliports and seaports too
|
||||
virtual FGPositioned::Type maxType() const
|
||||
{ return FGPositioned::SEAPORT; }
|
||||
private:
|
||||
static MetarAirportFilter * _instance;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
515
src/Environment/metarproperties.cxx
Normal file
515
src/Environment/metarproperties.cxx
Normal file
@@ -0,0 +1,515 @@
|
||||
// metarproperties.cxx -- Parse a METAR and write properties
|
||||
//
|
||||
// Written by David Megginson, started May 2002.
|
||||
// Rewritten by Torsten Dreyer, August 2010
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <cstring> // for strlen
|
||||
|
||||
#include "metarproperties.hxx"
|
||||
#include "fgmetar.hxx"
|
||||
#include "environment.hxx"
|
||||
#include "atmosphere.hxx"
|
||||
#include "metarairportfilter.hxx"
|
||||
#include <simgear/scene/sky/cloud.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/magvar/magvar.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace Environment {
|
||||
|
||||
static vector<string> coverage_string;
|
||||
|
||||
/**
|
||||
* @brief Helper class to wrap SGMagVar functionality and cache the variation and dip for
|
||||
* a certain position.
|
||||
*/
|
||||
class MagneticVariation : public SGMagVar {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
MagneticVariation() : _lat(1), _lon(1), _alt(1) {
|
||||
recalc( 0.0, 0.0, 0.0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get the magnetic variation for a specific position at the current time
|
||||
* @param lon the positions longitude in degrees
|
||||
* @param lat the positions latitude in degrees
|
||||
* @param alt the positions height above MSL (aka altitude) in feet
|
||||
* @return the magnetic variation in degrees
|
||||
*/
|
||||
double get_variation_deg( double lon, double lat, double alt );
|
||||
|
||||
/**
|
||||
* @brief get the magnetic dip for a specific position at the current time
|
||||
* @param lon the positions longitude in degrees
|
||||
* @param lat the positions latitude in degrees
|
||||
* @param alt the positions height above MSL (aka altitude) in feet
|
||||
* @return the magnetic dip in degrees
|
||||
*/
|
||||
double get_dip_deg( double lon, double lat, double alt );
|
||||
private:
|
||||
void recalc( double lon, double lat, double alt );
|
||||
SGTime _time;
|
||||
double _lat, _lon, _alt;
|
||||
};
|
||||
|
||||
inline void MagneticVariation::recalc( double lon, double lat, double alt )
|
||||
{
|
||||
// calculation of magnetic variation is expensive. Cache the position
|
||||
// and perform this calculation only if it has changed
|
||||
if( _lon != lon || _lat != lat || _alt != alt ) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "Recalculating magvar for lon=" << lon << ", lat=" << lat << ", alt=" << alt );
|
||||
_lon = lon;
|
||||
_lat = lat;
|
||||
_alt = alt;
|
||||
|
||||
SGGeod location(SGGeod::fromDegFt(lon, lat, alt));
|
||||
_time.update( location, 0, 0 );
|
||||
update( lon, lat, alt, _time.getJD() );
|
||||
}
|
||||
}
|
||||
|
||||
inline double MagneticVariation::get_variation_deg( double lon, double lat, double alt )
|
||||
{
|
||||
recalc( lon, lat, alt );
|
||||
return get_magvar() * SGD_RADIANS_TO_DEGREES;
|
||||
}
|
||||
|
||||
inline double MagneticVariation::get_dip_deg( double lon, double lat, double alt )
|
||||
{
|
||||
recalc( lon, lat, alt );
|
||||
return get_magdip() * SGD_RADIANS_TO_DEGREES;
|
||||
}
|
||||
|
||||
MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
|
||||
_rootNode(rootNode),
|
||||
_metarValidNode( rootNode->getNode( "valid", true ) ),
|
||||
_station_elevation(0.0),
|
||||
_station_latitude(0.0),
|
||||
_station_longitude(0.0),
|
||||
_min_visibility(16000.0),
|
||||
_max_visibility(16000.0),
|
||||
_base_wind_dir(0),
|
||||
_base_wind_range_from(0),
|
||||
_base_wind_range_to(0),
|
||||
_wind_speed(0.0),
|
||||
_wind_from_north_fps(0.0),
|
||||
_wind_from_east_fps(0.0),
|
||||
_gusts(0.0),
|
||||
_temperature(0.0),
|
||||
_dewpoint(0.0),
|
||||
_humidity(0.0),
|
||||
_pressure(0.0),
|
||||
_sea_level_temperature(0.0),
|
||||
_sea_level_dewpoint(0.0),
|
||||
_sea_level_pressure(29.92),
|
||||
_rain(0.0),
|
||||
_hail(0.0),
|
||||
_snow(0.0),
|
||||
_snow_cover(false),
|
||||
_day(0),
|
||||
_hour(0),
|
||||
_minute(0),
|
||||
_cavok(false),
|
||||
_magneticVariation(new MagneticVariation())
|
||||
{
|
||||
// Hack to avoid static initialization order problems on OSX
|
||||
if( coverage_string.empty() ) {
|
||||
coverage_string.push_back(SGCloudLayer::SG_CLOUD_CLEAR_STRING);
|
||||
coverage_string.push_back(SGCloudLayer::SG_CLOUD_FEW_STRING);
|
||||
coverage_string.push_back(SGCloudLayer::SG_CLOUD_SCATTERED_STRING);
|
||||
coverage_string.push_back(SGCloudLayer::SG_CLOUD_BROKEN_STRING);
|
||||
coverage_string.push_back(SGCloudLayer::SG_CLOUD_OVERCAST_STRING);
|
||||
}
|
||||
// don't tie metar-valid, so listeners get triggered
|
||||
_metarValidNode->setBoolValue( false );
|
||||
_tiedProperties.setRoot( _rootNode );
|
||||
_tiedProperties.Tie("data", this, &MetarProperties::get_metar, &MetarProperties::set_metar );
|
||||
_tiedProperties.Tie("station-id", this, &MetarProperties::get_station_id, &MetarProperties::set_station_id );
|
||||
_tiedProperties.Tie("station-elevation-ft", &_station_elevation );
|
||||
_tiedProperties.Tie("station-latitude-deg", &_station_latitude );
|
||||
_tiedProperties.Tie("station-longitude-deg", &_station_longitude );
|
||||
_tiedProperties.Tie("station-magnetic-variation-deg", this, &MetarProperties::get_magnetic_variation_deg );
|
||||
_tiedProperties.Tie("station-magnetic-dip-deg", this, &MetarProperties::get_magnetic_dip_deg );
|
||||
_tiedProperties.Tie("min-visibility-m", &_min_visibility );
|
||||
_tiedProperties.Tie("max-visibility-m", &_max_visibility );
|
||||
_tiedProperties.Tie("base-wind-range-from", &_base_wind_range_from );
|
||||
_tiedProperties.Tie("base-wind-range-to", &_base_wind_range_to );
|
||||
_tiedProperties.Tie("base-wind-speed-kt", this, &MetarProperties::get_wind_speed, &MetarProperties::set_wind_speed );
|
||||
_tiedProperties.Tie("base-wind-dir-deg", this, &MetarProperties::get_base_wind_dir, &MetarProperties::set_base_wind_dir );
|
||||
_tiedProperties.Tie("base-wind-from-north-fps", this, &MetarProperties::get_wind_from_north_fps, &MetarProperties::set_wind_from_north_fps );
|
||||
_tiedProperties.Tie("base-wind-from-east-fps",this, &MetarProperties::get_wind_from_east_fps, &MetarProperties::set_wind_from_east_fps );
|
||||
_tiedProperties.Tie("gust-wind-speed-kt", &_gusts );
|
||||
_tiedProperties.Tie("temperature-degc", &_temperature );
|
||||
_tiedProperties.Tie("dewpoint-degc", &_dewpoint );
|
||||
_tiedProperties.Tie("rel-humidity-norm", &_humidity );
|
||||
_tiedProperties.Tie("pressure-inhg", &_pressure );
|
||||
_tiedProperties.Tie("temperature-sea-level-degc", &_sea_level_temperature );
|
||||
_tiedProperties.Tie("dewpoint-sea-level-degc", &_sea_level_dewpoint );
|
||||
_tiedProperties.Tie("pressure-sea-level-inhg", &_sea_level_pressure );
|
||||
_tiedProperties.Tie("rain-norm", &_rain );
|
||||
_tiedProperties.Tie("hail-norm", &_hail );
|
||||
_tiedProperties.Tie("snow-norm", &_snow);
|
||||
_tiedProperties.Tie("snow-cover", &_snow_cover );
|
||||
_tiedProperties.Tie("day", &_day );
|
||||
_tiedProperties.Tie("hour", &_hour );
|
||||
_tiedProperties.Tie("minute", &_minute );
|
||||
_tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded );
|
||||
_tiedProperties.Tie("cavok", &_cavok );
|
||||
_tiedProperties.Tie("description", this, &MetarProperties::get_description );
|
||||
|
||||
// mark proeprties as listener-safe, we invoke valueChanged explicitly
|
||||
_tiedProperties.setAttribute(SGPropertyNode::LISTENER_SAFE, true);
|
||||
}
|
||||
|
||||
MetarProperties::~MetarProperties()
|
||||
{
|
||||
delete _magneticVariation;
|
||||
}
|
||||
|
||||
void MetarProperties::invalidate()
|
||||
{
|
||||
if( _metarValidNode->getBoolValue() )
|
||||
_metarValidNode->setBoolValue(false);
|
||||
}
|
||||
|
||||
static const double thickness_value[] = { 0, 65, 600, 750, 1000 };
|
||||
|
||||
const char* MetarProperties::get_metar() const
|
||||
{
|
||||
if (!_metar || _metarData.empty())
|
||||
return "";
|
||||
return _metarData.c_str();
|
||||
}
|
||||
|
||||
void MetarProperties::set_metar( const char * metarString )
|
||||
{
|
||||
SGSharedPtr<FGMetar> m;
|
||||
if (!metarString) {
|
||||
setMetar(m);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string trimmedMetar = simgear::strutils::strip(std::string{metarString});
|
||||
if (trimmedMetar.empty()) {
|
||||
setMetar(m);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m = new FGMetar(trimmedMetar);
|
||||
}
|
||||
catch( sg_io_exception& ) {
|
||||
SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar:'" << trimmedMetar << "'");
|
||||
_metarValidNode->setBoolValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setMetar(m);
|
||||
}
|
||||
|
||||
void MetarProperties::setMetar( SGSharedPtr<FGMetar> m )
|
||||
{
|
||||
_metar = m;
|
||||
_decoded.clear();
|
||||
if (!m) {
|
||||
_metarData.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the string so we have guranteed storage for get_metar tied property API
|
||||
_metarData = _metar->getDataString();
|
||||
|
||||
const vector<string> weather = m->getWeather();
|
||||
for( vector<string>::const_iterator it = weather.begin(); it != weather.end(); ++it ) {
|
||||
if( !_decoded.empty() ) _decoded.append(", ");
|
||||
_decoded.append(*it);
|
||||
}
|
||||
|
||||
_min_visibility = m->getMinVisibility().getVisibility_m();
|
||||
_max_visibility = m->getMaxVisibility().getVisibility_m();
|
||||
|
||||
const SGMetarVisibility *dirvis = m->getDirVisibility();
|
||||
for ( int i = 0; i < 8; i++, dirvis++) {
|
||||
SGPropertyNode *vis = _rootNode->getChild("visibility", i, true);
|
||||
double v = dirvis->getVisibility_m();
|
||||
|
||||
vis->setDoubleValue("min-m", v);
|
||||
vis->setDoubleValue("max-m", v);
|
||||
}
|
||||
|
||||
set_base_wind_dir(m->getWindDir());
|
||||
_base_wind_range_from = m->getWindRangeFrom();
|
||||
_base_wind_range_to = m->getWindRangeTo();
|
||||
set_wind_speed(m->getWindSpeed_kt());
|
||||
|
||||
_gusts = m->getGustSpeed_kt();
|
||||
_temperature = m->getTemperature_C();
|
||||
_dewpoint = m->getDewpoint_C();
|
||||
_humidity = m->getRelHumidity();
|
||||
_pressure = m->getPressure_inHg();
|
||||
|
||||
{
|
||||
// 1. check the id given in the metar
|
||||
FGAirport* a = FGAirport::findByIdent(m->getId());
|
||||
|
||||
// 2. if unknown, find closest airport with metar to current position
|
||||
if( a == NULL ) {
|
||||
SGGeod pos = SGGeod::fromDeg(
|
||||
fgGetDouble( "/position/longitude-deg", 0.0 ),
|
||||
fgGetDouble( "/position/latitude-deg", 0.0 ) );
|
||||
a = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
|
||||
}
|
||||
|
||||
// 3. otherwise use ground elevation
|
||||
if( a != NULL ) {
|
||||
_station_elevation = a->getElevation();
|
||||
const SGGeod & towerPosition = a->getTowerLocation();
|
||||
_station_latitude = towerPosition.getLatitudeDeg();
|
||||
_station_longitude = towerPosition.getLongitudeDeg();
|
||||
_station_id = a->ident();
|
||||
} else {
|
||||
_station_elevation = fgGetDouble("/position/ground-elev-m", 0.0 ) * SG_METER_TO_FEET;
|
||||
_station_latitude = fgGetDouble( "/position/latitude-deg", 0.0 );
|
||||
_station_longitude = fgGetDouble( "/position/longitude-deg", 0.0 );
|
||||
_station_id = "XXXX";
|
||||
}
|
||||
}
|
||||
|
||||
{ // calculate sea level temperature, dewpoint and pressure
|
||||
FGEnvironment dummy; // instantiate a dummy so we can leech a method
|
||||
dummy.set_elevation_ft( _station_elevation );
|
||||
dummy.set_temperature_degc( _temperature );
|
||||
dummy.set_dewpoint_degc( _dewpoint );
|
||||
_sea_level_temperature = dummy.get_temperature_sea_level_degc();
|
||||
_sea_level_dewpoint = dummy.get_dewpoint_sea_level_degc();
|
||||
|
||||
double elevation_m = _station_elevation * SG_FEET_TO_METER;
|
||||
double fieldPressure = FGAtmo::fieldPressure( elevation_m, _pressure * atmodel::inHg );
|
||||
_sea_level_pressure = P_layer(0, elevation_m, fieldPressure, _temperature + atmodel::freezing, atmodel::ISA::lam0) / atmodel::inHg;
|
||||
}
|
||||
|
||||
bool isBC = false;
|
||||
bool isBR = false;
|
||||
bool isFG = false;
|
||||
bool isMI = false;
|
||||
bool isHZ = false;
|
||||
|
||||
{
|
||||
for( unsigned i = 0; i < 3; i++ ) {
|
||||
SGPropertyNode_ptr n = _rootNode->getChild("weather", i, true );
|
||||
vector<struct SGMetar::Weather> weather = m->getWeather2();
|
||||
struct SGMetar::Weather * w = i < weather.size() ? &weather[i] : NULL;
|
||||
n->getNode("intensity",true)->setIntValue( w != NULL ? w->intensity : 0 );
|
||||
n->getNode("vincinity",true)->setBoolValue( w != NULL ? w->vincinity : false );
|
||||
for( unsigned j = 0; j < 3; j++ ) {
|
||||
|
||||
const string & phenomenon = w != NULL && j < w->phenomena.size() ? w->phenomena[j].c_str() : "";
|
||||
n->getChild( "phenomenon", j, true )->setStringValue( phenomenon );
|
||||
|
||||
const string & description = w != NULL && j < w->descriptions.size() ? w->descriptions[j].c_str() : "";
|
||||
n->getChild( "description", j, true )->setStringValue( description );
|
||||
|
||||
// need to know later,
|
||||
// if its fog(FG) (might be shallow(MI) or patches(BC)) or haze (HZ) or mist(BR)
|
||||
if( phenomenon == "FG" ) isFG = true;
|
||||
if( phenomenon == "HZ" ) isHZ = true;
|
||||
if( phenomenon == "BR" ) isBR = true;
|
||||
if( description == "MI" ) isMI = true;
|
||||
if( description == "BC" ) isBC = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
static const char * LAYER = "layer";
|
||||
SGPropertyNode_ptr cloudsNode = _rootNode->getNode("clouds", true );
|
||||
const vector<SGMetarCloud> & metarClouds = m->getClouds();
|
||||
unsigned layerOffset = 0; // Oh, this is ugly!
|
||||
|
||||
// fog/mist/haze cloud layer does not work with 3d clouds yet :-(
|
||||
bool setGroundCloudLayer = _rootNode->getBoolValue("set-ground-cloud-layer", false ) &&
|
||||
!fgGetBool("/sim/rendering/clouds3d-enable", false);
|
||||
|
||||
// track the coverage of the previous layer, so we can use it
|
||||
// for higher layers which don't have coverage set
|
||||
// see: https://sourceforge.net/p/flightgear/codetickets/2765/
|
||||
SGMetarCloud::Coverage coverageBelow = SGMetarCloud::COVERAGE_NIL;
|
||||
|
||||
if( setGroundCloudLayer ) {
|
||||
// create a cloud layer #0 starting at the ground if its fog, mist or haze
|
||||
|
||||
// make sure layer actually starts at ground and set it's bottom at a constant
|
||||
// value below the station's elevation
|
||||
const double LAYER_BOTTOM_STATION_OFFSET =
|
||||
fgGetDouble( "/environment/params/fog-mist-haze-layer/offset-from-station-elevation-ft", -200 );
|
||||
|
||||
SGMetarCloud::Coverage coverage = SGMetarCloud::COVERAGE_NIL;
|
||||
double thickness = 0;
|
||||
double alpha = 1.0;
|
||||
|
||||
if( isFG ) { // fog
|
||||
coverage = SGMetarCloud::getCoverage( isBC ?
|
||||
fgGetString( "/environment/params/fog-mist-haze-layer/fog-bc-2dlayer-coverage", SGMetarCloud::COVERAGE_SCATTERED_STRING ) :
|
||||
fgGetString( "/environment/params/fog-mist-haze-layer/fog-2dlayer-coverage", SGMetarCloud::COVERAGE_BROKEN_STRING )
|
||||
);
|
||||
|
||||
thickness = isMI ?
|
||||
fgGetDouble("/environment/params/fog-mist-haze-layer/fog-shallow-thickness-ft",30) - LAYER_BOTTOM_STATION_OFFSET : // shallow fog, 10m/30ft
|
||||
fgGetDouble("/environment/params/fog-mist-haze-layer/fog-thickness-ft",500) - LAYER_BOTTOM_STATION_OFFSET; // fog, 150m/500ft
|
||||
alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/fog-2dlayer-alpha", 1.0);
|
||||
} else if( isBR ) { // mist
|
||||
coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
|
||||
thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
|
||||
alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/mist-2dlayer-alpha",0.8);
|
||||
} else if( isHZ ) { // haze
|
||||
coverage = SGMetarCloud::getCoverage(fgGetString("/environment/params/fog-mist-haze-layer/mist-2dlayer-coverage", SGMetarCloud::COVERAGE_OVERCAST_STRING));
|
||||
thickness = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-thickness-ft",2000) - LAYER_BOTTOM_STATION_OFFSET;
|
||||
alpha = fgGetDouble("/environment/params/fog-mist-haze-layer/haze-2dlayer-alpha",0.6);
|
||||
}
|
||||
|
||||
if( coverage != SGMetarCloud::COVERAGE_NIL ) {
|
||||
|
||||
// if there is a layer above the fog, limit the top to one foot below that layer's bottom
|
||||
if( metarClouds.size() > 0 && metarClouds[0].getCoverage() != SGMetarCloud::COVERAGE_CLEAR )
|
||||
thickness = metarClouds[0].getAltitude_ft() - LAYER_BOTTOM_STATION_OFFSET - 1;
|
||||
|
||||
SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, 0, true );
|
||||
layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
|
||||
layerNode->setStringValue( "coverage", coverage_string[coverage] );
|
||||
layerNode->setDoubleValue( "elevation-ft", _station_elevation + LAYER_BOTTOM_STATION_OFFSET );
|
||||
layerNode->setDoubleValue( "thickness-ft", thickness );
|
||||
layerNode->setDoubleValue( "visibility-m", _min_visibility );
|
||||
layerNode->setDoubleValue( "alpha", alpha );
|
||||
_min_visibility = _max_visibility =
|
||||
fgGetDouble("/environment/params/fog-mist-haze-layer/visibility-above-layer-m",20000.0); // assume good visibility above the fog
|
||||
layerOffset = 1; // shudder
|
||||
|
||||
coverageBelow = coverage;
|
||||
}
|
||||
}
|
||||
|
||||
for( unsigned i = 0; i < 5-layerOffset; i++ ) {
|
||||
SGPropertyNode_ptr layerNode = cloudsNode->getChild(LAYER, i+layerOffset, true );
|
||||
SGMetarCloud::Coverage coverage = i < metarClouds.size() ? metarClouds[i].getCoverage() : SGMetarCloud::COVERAGE_CLEAR;
|
||||
if (coverage == SGMetarCloud::COVERAGE_NIL) {
|
||||
coverage = coverageBelow; // invalid coverage, use value of layer below
|
||||
} else {
|
||||
coverageBelow = coverage; // valid coverage, save for future layers
|
||||
}
|
||||
|
||||
if (coverage == SGMetarCloud::COVERAGE_NIL) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_WARN, "METAR: skipping cloud layer " << i << " becuase no coverage is set");
|
||||
continue;
|
||||
}
|
||||
|
||||
double elevation =
|
||||
i >= metarClouds.size() || coverage == SGMetarCloud::COVERAGE_CLEAR ?
|
||||
-9999.0 :
|
||||
metarClouds[i].getAltitude_ft() + _station_elevation;
|
||||
|
||||
layerNode->setDoubleValue( "alpha", 1.0 );
|
||||
layerNode->setStringValue( "coverage", coverage_string[coverage] );
|
||||
layerNode->setDoubleValue( "coverage-type", SGCloudLayer::getCoverageType(coverage_string[coverage]) );
|
||||
layerNode->setDoubleValue( "elevation-ft", elevation );
|
||||
layerNode->setDoubleValue( "thickness-ft", thickness_value[coverage]);
|
||||
layerNode->setDoubleValue( "span-m", 40000 );
|
||||
layerNode->setDoubleValue( "visibility-m", 50.0 );
|
||||
}
|
||||
}
|
||||
|
||||
_rain = m->getRain();
|
||||
_hail = m->getHail();
|
||||
_snow = m->getSnow();
|
||||
_snow_cover = m->getSnowCover();
|
||||
_day = m->getDay();
|
||||
_hour = m->getHour();
|
||||
_minute = m->getMinute();
|
||||
_cavok = m->getCAVOK();
|
||||
_tiedProperties.fireValueChanged();
|
||||
_metarValidNode->setBoolValue(true);
|
||||
_description = m->getDescription(-1);
|
||||
}
|
||||
|
||||
void MetarProperties::setStationId( const std::string & value )
|
||||
{
|
||||
set_station_id(simgear::strutils::strip(value).c_str());
|
||||
}
|
||||
|
||||
double MetarProperties::get_magnetic_variation_deg() const
|
||||
{
|
||||
return _magneticVariation->get_variation_deg( _station_longitude, _station_latitude, _station_elevation );
|
||||
}
|
||||
|
||||
double MetarProperties::get_magnetic_dip_deg() const
|
||||
{
|
||||
return _magneticVariation->get_dip_deg( _station_longitude, _station_latitude, _station_elevation );
|
||||
}
|
||||
|
||||
static inline void calc_wind_hs( double north_fps, double east_fps, int & heading_deg, double & speed_kt )
|
||||
{
|
||||
speed_kt = sqrt((north_fps)*(north_fps)+(east_fps)*(east_fps)) * 3600.0 / (SG_NM_TO_METER * SG_METER_TO_FEET);
|
||||
heading_deg = SGMiscd::roundToInt(
|
||||
SGMiscd::normalizeAngle2( atan2( east_fps, north_fps ) ) * SGD_RADIANS_TO_DEGREES );
|
||||
}
|
||||
|
||||
void MetarProperties::set_wind_from_north_fps( double value )
|
||||
{
|
||||
_wind_from_north_fps = value;
|
||||
calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
|
||||
}
|
||||
|
||||
void MetarProperties::set_wind_from_east_fps( double value )
|
||||
{
|
||||
_wind_from_east_fps = value;
|
||||
calc_wind_hs( _wind_from_north_fps, _wind_from_east_fps, _base_wind_dir, _wind_speed );
|
||||
}
|
||||
|
||||
static inline void calc_wind_ne( double heading_deg, double speed_kt, double & north_fps, double & east_fps )
|
||||
{
|
||||
double speed_fps = speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0;
|
||||
north_fps = speed_fps * cos(heading_deg * SGD_DEGREES_TO_RADIANS);
|
||||
east_fps = speed_fps * sin(heading_deg * SGD_DEGREES_TO_RADIANS);
|
||||
}
|
||||
|
||||
void MetarProperties::set_base_wind_dir( double value )
|
||||
{
|
||||
_base_wind_dir = value;
|
||||
calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
|
||||
}
|
||||
|
||||
void MetarProperties::set_wind_speed( double value )
|
||||
{
|
||||
_wind_speed = value;
|
||||
calc_wind_ne( (double)_base_wind_dir, _wind_speed, _wind_from_north_fps, _wind_from_east_fps );
|
||||
}
|
||||
|
||||
|
||||
} // namespace Environment
|
||||
115
src/Environment/metarproperties.hxx
Normal file
115
src/Environment/metarproperties.hxx
Normal file
@@ -0,0 +1,115 @@
|
||||
// metarproperties.hxx -- Parse a METAR and write properties
|
||||
//
|
||||
// Written by David Megginson, started May 2002.
|
||||
// Rewritten by Torsten Dreyer, August 2010
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef __METARPROPERTIES_HXX
|
||||
#define __METARPROPERTIES_HXX
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
class FGMetar;
|
||||
|
||||
namespace Environment {
|
||||
|
||||
class MagneticVariation;
|
||||
|
||||
class MetarProperties : public SGReferenced
|
||||
{
|
||||
public:
|
||||
MetarProperties( SGPropertyNode_ptr rootNode );
|
||||
virtual ~MetarProperties();
|
||||
|
||||
SGPropertyNode_ptr get_root_node() const { return _rootNode; }
|
||||
virtual bool isValid() const { return _metarValidNode->getBoolValue(); }
|
||||
virtual const std::string & getStationId() const { return _station_id; }
|
||||
virtual void setStationId( const std::string & value );
|
||||
virtual void setMetar(SGSharedPtr<FGMetar> m);
|
||||
virtual void invalidate();
|
||||
|
||||
private:
|
||||
const char * get_metar() const;
|
||||
void set_metar( const char * metar );
|
||||
|
||||
const char * get_station_id() const { return _station_id.c_str(); }
|
||||
void set_station_id( const char * value );
|
||||
const char * get_decoded() const { return _decoded.c_str(); }
|
||||
const char * get_description() const { return _description.c_str(); }
|
||||
double get_magnetic_variation_deg() const;
|
||||
double get_magnetic_dip_deg() const;
|
||||
double get_wind_from_north_fps() const { return _wind_from_north_fps; }
|
||||
double get_wind_from_east_fps() const { return _wind_from_east_fps; }
|
||||
double get_base_wind_dir() const { return _base_wind_dir; }
|
||||
double get_wind_speed() const { return _wind_speed; }
|
||||
void set_wind_from_north_fps( double value );
|
||||
void set_wind_from_east_fps( double value );
|
||||
void set_base_wind_dir( double value );
|
||||
void set_wind_speed( double value );
|
||||
|
||||
SGSharedPtr<FGMetar> _metar;
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
SGPropertyNode_ptr _metarValidNode;
|
||||
|
||||
std::string _metarData;
|
||||
|
||||
std::string _station_id;
|
||||
double _station_elevation;
|
||||
double _station_latitude;
|
||||
double _station_longitude;
|
||||
double _min_visibility;
|
||||
double _max_visibility;
|
||||
int _base_wind_dir;
|
||||
int _base_wind_range_from;
|
||||
int _base_wind_range_to;
|
||||
double _wind_speed;
|
||||
double _wind_from_north_fps;
|
||||
double _wind_from_east_fps;
|
||||
double _gusts;
|
||||
double _temperature;
|
||||
double _dewpoint;
|
||||
double _humidity;
|
||||
double _pressure;
|
||||
double _sea_level_temperature;
|
||||
double _sea_level_dewpoint;
|
||||
double _sea_level_pressure;
|
||||
double _rain;
|
||||
double _hail;
|
||||
double _snow;
|
||||
bool _snow_cover;
|
||||
std::string _decoded;
|
||||
int _day;
|
||||
int _hour;
|
||||
int _minute;
|
||||
bool _cavok;
|
||||
std::string _description;
|
||||
protected:
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
MagneticVariation * _magneticVariation;
|
||||
};
|
||||
|
||||
inline void MetarProperties::set_station_id( const char * value )
|
||||
{
|
||||
_station_id = value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif // __METARPROPERTIES_HXX
|
||||
301
src/Environment/precipitation_mgr.cxx
Normal file
301
src/Environment/precipitation_mgr.cxx
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @file precipitation_mgr.cxx
|
||||
* @author Nicolas VIVIEN
|
||||
* @date 2008-02-10
|
||||
*
|
||||
* @note Copyright (C) 2008 Nicolas VIVIEN
|
||||
*
|
||||
* @brief Precipitation manager
|
||||
* This manager calculate the intensity of precipitation in function of the altitude,
|
||||
* calculate the wind direction and velocity, then update the drawing of precipitation.
|
||||
*
|
||||
* @par Licences
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <osg/MatrixTransform>
|
||||
|
||||
#include <simgear/constants.h>
|
||||
#include <simgear/scene/sky/sky.hxx>
|
||||
#include <simgear/scene/sky/cloud.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include <Viewer/renderer.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
|
||||
#include "precipitation_mgr.hxx"
|
||||
|
||||
/**
|
||||
* @brief FGPrecipitation Manager constructor
|
||||
*
|
||||
* Build a new object to manage the precipitation object
|
||||
*/
|
||||
FGPrecipitationMgr::FGPrecipitationMgr()
|
||||
{
|
||||
// Try to set up the scenegraph.
|
||||
setupSceneGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FGPrecipitaiton Manager destructor
|
||||
*/
|
||||
FGPrecipitationMgr::~FGPrecipitationMgr()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SGSubsystem initialization
|
||||
*/
|
||||
void FGPrecipitationMgr::init()
|
||||
{
|
||||
// Read latitude and longitude position
|
||||
SGGeod geod = SGGeod::fromDegM(fgGetDouble("/position/longitude-deg", 0.0),
|
||||
fgGetDouble("/position/latitude-deg", 0.0),
|
||||
0.0);
|
||||
osg::Matrix position(makeZUpFrame(geod));
|
||||
// Move the precipitation object to player position
|
||||
transform->setMatrix(position);
|
||||
fgGetNode("environment/params/precipitation-level-ft", true);
|
||||
}
|
||||
|
||||
void FGPrecipitationMgr::bind ()
|
||||
{
|
||||
_tiedProperties.setRoot( fgGetNode("/sim/rendering", true ) );
|
||||
_tiedProperties.Tie("precipitation-enable", precipitation.get(),
|
||||
&SGPrecipitation::getEnabled,
|
||||
&SGPrecipitation::setEnabled);
|
||||
}
|
||||
|
||||
void FGPrecipitationMgr::unbind ()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
// Set up the precipitation manager scenegraph.
|
||||
void FGPrecipitationMgr::setupSceneGraph(void)
|
||||
{
|
||||
FGScenery* scenery = globals->get_scenery();
|
||||
osg::Group* group = scenery->get_precipitation_branch();
|
||||
transform = new osg::MatrixTransform();
|
||||
precipitation = new SGPrecipitation();
|
||||
|
||||
|
||||
// By default, no precipitation
|
||||
precipitation->setRainIntensity(0);
|
||||
precipitation->setSnowIntensity(0);
|
||||
|
||||
// set the clip distance from the config
|
||||
precipitation->setClipDistance(fgGetFloat("/environment/precipitation-control/clip-distance",5.0));
|
||||
transform->addChild(precipitation->build());
|
||||
group->addChild(transform.get());
|
||||
}
|
||||
|
||||
|
||||
void FGPrecipitationMgr::setPrecipitationLevel(double a)
|
||||
{
|
||||
fgSetDouble("environment/params/precipitation-level-ft",a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the max alitutude with precipitation
|
||||
*
|
||||
* @returns Elevation max in meter
|
||||
*
|
||||
* This function permits you to know what is the altitude max where we can
|
||||
* find precipitation. The value is returned in meters.
|
||||
*/
|
||||
float FGPrecipitationMgr::getPrecipitationAtAltitudeMax(void)
|
||||
{
|
||||
int i;
|
||||
int max;
|
||||
float result;
|
||||
SGPropertyNode *boundaryNode, *boundaryEntry;
|
||||
|
||||
if (fgGetBool("/environment/params/use-external-precipitation-level", false)) {
|
||||
// If we're not modeling the precipitation level based on the cloud
|
||||
// layers, take it directly from the property tree.
|
||||
return fgGetFloat("/environment/params/external-precipitation-level-m", 0.0);
|
||||
}
|
||||
|
||||
|
||||
// By default (not cloud layer)
|
||||
max = SGCloudLayer::SG_MAX_CLOUD_COVERAGES;
|
||||
result = 0;
|
||||
|
||||
SGSky* thesky = globals->get_renderer()->getSky();
|
||||
|
||||
// To avoid messing up
|
||||
if (thesky == NULL)
|
||||
return result;
|
||||
|
||||
// For each cloud layer
|
||||
for (i=0; i<thesky->get_cloud_layer_count(); i++) {
|
||||
int q;
|
||||
|
||||
// Get coverage
|
||||
// Value for q are (meaning / thickness) :
|
||||
// 5 : "clear" / 0
|
||||
// 4 : "cirrus" / ??
|
||||
// 3 : "few" / 65
|
||||
// 2 : "scattered" / 600
|
||||
// 1 : "broken" / 750
|
||||
// 0 : "overcast" / 1000
|
||||
q = thesky->get_cloud_layer(i)->getCoverage();
|
||||
|
||||
// Save the coverage max
|
||||
if (q < max) {
|
||||
max = q;
|
||||
result = thesky->get_cloud_layer(i)->getElevation_m();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we haven't found clouds layers, we read the bounday layers table.
|
||||
if (result > 0)
|
||||
return result;
|
||||
|
||||
|
||||
// Read boundary layers node
|
||||
boundaryNode = fgGetNode("/environment/config/boundary");
|
||||
|
||||
if (boundaryNode != NULL) {
|
||||
i = 0;
|
||||
|
||||
// For each boundary layers
|
||||
while ( ( boundaryEntry = boundaryNode->getNode( "entry", i ) ) != NULL ) {
|
||||
double elev = boundaryEntry->getDoubleValue( "elevation-ft" );
|
||||
|
||||
if (elev > result)
|
||||
result = elev;
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the result in meter
|
||||
result = result * SG_FEET_TO_METER;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Update the precipitation drawing
|
||||
*
|
||||
* To seem real, we stop the precipitation above the cloud or boundary layer.
|
||||
* If METAR information doesn't give us this altitude, we will see precipitations
|
||||
* in space...
|
||||
* Moreover, below 0°C we change rain into snow.
|
||||
*/
|
||||
void FGPrecipitationMgr::update(double dt)
|
||||
{
|
||||
double dewtemp;
|
||||
double currtemp;
|
||||
double rain_intensity;
|
||||
double snow_intensity;
|
||||
|
||||
float altitudeAircraft;
|
||||
float altitudeCloudLayer;
|
||||
float rainDropletSize;
|
||||
float snowFlakeSize;
|
||||
float illumination;
|
||||
|
||||
altitudeCloudLayer = this->getPrecipitationAtAltitudeMax() * SG_METER_TO_FEET;
|
||||
setPrecipitationLevel(altitudeCloudLayer);
|
||||
|
||||
|
||||
|
||||
// Does the user enable the precipitation ?
|
||||
if (!precipitation->getEnabled() ) {
|
||||
// Disable precipitations
|
||||
precipitation->setRainIntensity(0);
|
||||
precipitation->setSnowIntensity(0);
|
||||
|
||||
// Update the drawing...
|
||||
precipitation->update();
|
||||
|
||||
// Exit
|
||||
return;
|
||||
}
|
||||
|
||||
// See if external droplet size and illumination are used
|
||||
if (fgGetBool("/environment/precipitation-control/detailed-precipitation", false)) {
|
||||
precipitation->setDropletExternal(true);
|
||||
rainDropletSize = fgGetFloat("/environment/precipitation-control/rain-droplet-size", 0.015);
|
||||
snowFlakeSize = fgGetFloat("/environment/precipitation-control/snow-flake-size", 0.03);
|
||||
illumination = fgGetFloat("/environment/precipitation-control/illumination", 1.0);
|
||||
precipitation->setRainDropletSize(rainDropletSize);
|
||||
precipitation->setSnowFlakeSize(snowFlakeSize);
|
||||
precipitation->setIllumination(illumination);
|
||||
}
|
||||
|
||||
// Get the elevation of aicraft and of the cloud layer
|
||||
altitudeAircraft = fgGetDouble("/position/altitude-ft", 0.0);
|
||||
|
||||
if ((altitudeCloudLayer > 0) && (altitudeAircraft > altitudeCloudLayer)) {
|
||||
// The aircraft is above the cloud layer
|
||||
rain_intensity = 0;
|
||||
snow_intensity = 0;
|
||||
}
|
||||
else {
|
||||
// The aircraft is bellow the cloud layer
|
||||
rain_intensity = fgGetDouble("/environment/rain-norm", 0.0);
|
||||
snow_intensity = fgGetDouble("/environment/snow-norm", 0.0);
|
||||
}
|
||||
|
||||
// Get the current and dew temperature
|
||||
dewtemp = fgGetDouble("/environment/dewpoint-degc", 0.0);
|
||||
currtemp = fgGetDouble("/environment/temperature-degc", 0.0);
|
||||
|
||||
if (currtemp < dewtemp) {
|
||||
// There is fog... and the weather is very steamy
|
||||
if (rain_intensity == 0)
|
||||
rain_intensity = 0.15;
|
||||
}
|
||||
|
||||
// If the current temperature is below 0°C, we turn off the rain to snow...
|
||||
if (currtemp < 0)
|
||||
precipitation->setFreezing(true);
|
||||
else
|
||||
precipitation->setFreezing(false);
|
||||
|
||||
|
||||
// Set the wind property
|
||||
precipitation->setWindProperty(
|
||||
fgGetDouble("/environment/wind-from-heading-deg", 0.0),
|
||||
fgGetDouble("/environment/wind-speed-kt", 0.0));
|
||||
|
||||
// Set the intensity of precipitation
|
||||
precipitation->setRainIntensity(rain_intensity);
|
||||
precipitation->setSnowIntensity(snow_intensity);
|
||||
|
||||
// Update the drawing...
|
||||
precipitation->update();
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGPrecipitationMgr> registrantFGPrecipitationMgr(
|
||||
SGSubsystemMgr::GENERAL,
|
||||
{{"FGScenery", SGSubsystemMgr::Dependency::HARD},
|
||||
{"SGSky", SGSubsystemMgr::Dependency::NONSUBSYSTEM_HARD}});
|
||||
61
src/Environment/precipitation_mgr.hxx
Normal file
61
src/Environment/precipitation_mgr.hxx
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file precipitation_mgr.hxx
|
||||
* @author Nicolas VIVIEN
|
||||
* @date 2008-02-10
|
||||
*
|
||||
* @note Copyright (C) 2008 Nicolas VIVIEN
|
||||
*
|
||||
* @brief Precipitation manager
|
||||
* This manager calculate the intensity of precipitation in function of the altitude,
|
||||
* calculate the wind direction and velocity, then update the drawing of precipitation.
|
||||
*
|
||||
* @par Licences
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PRECIPITATION_MGR_HXX
|
||||
#define _PRECIPITATION_MGR_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/environment/precipitation.hxx>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
class FGPrecipitationMgr : public SGSubsystem
|
||||
{
|
||||
private:
|
||||
osg::ref_ptr<osg::MatrixTransform> transform;
|
||||
osg::ref_ptr<SGPrecipitation> precipitation;
|
||||
float getPrecipitationAtAltitudeMax(void);
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
|
||||
public:
|
||||
FGPrecipitationMgr();
|
||||
virtual ~FGPrecipitationMgr();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "precipitation"; }
|
||||
|
||||
void setupSceneGraph(void);
|
||||
void setPrecipitationLevel(double l);
|
||||
};
|
||||
|
||||
#endif
|
||||
134
src/Environment/presets.cxx
Normal file
134
src/Environment/presets.cxx
Normal file
@@ -0,0 +1,134 @@
|
||||
// presets.cxx -- Wrap environment presets
|
||||
//
|
||||
// Written by Torsten Dreyer, January 2011
|
||||
//
|
||||
// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
#include "presets.hxx"
|
||||
|
||||
#include <cmath>
|
||||
#include <simgear/math/SGMisc.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
namespace Environment {
|
||||
namespace Presets {
|
||||
|
||||
PresetBase::PresetBase( const char * overrideNodePath )
|
||||
: _overrideNodePath( overrideNodePath )
|
||||
{
|
||||
}
|
||||
|
||||
void PresetBase::setOverride( bool value )
|
||||
{
|
||||
/*
|
||||
Don't initialize node in constructor because the class is used as a singleton
|
||||
and created as a static variable in the initialization sequence when globals()
|
||||
is not yet initialized and returns null.
|
||||
*/
|
||||
if( _overrideNode == NULL )
|
||||
_overrideNode = fgGetNode( _overrideNodePath.c_str(), true );
|
||||
_overrideNode->setBoolValue( value );
|
||||
}
|
||||
|
||||
|
||||
Wind::Wind() :
|
||||
PresetBase("/environment/config/presets/wind-override")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void Wind::preset( double min_hdg, double max_hdg, double speed_kt, double gust_kt )
|
||||
{
|
||||
// see: PresetBase::setOverride()
|
||||
|
||||
//TODO: handle variable wind and gusts
|
||||
if( _fromNorthNode == NULL )
|
||||
_fromNorthNode = fgGetNode("/environment/config/presets/wind-from-north-fps", true );
|
||||
|
||||
if( _fromEastNode == NULL )
|
||||
_fromEastNode = fgGetNode("/environment/config/presets/wind-from-east-fps", true );
|
||||
|
||||
double avgHeading_rad =
|
||||
SGMiscd::normalizeAngle2(
|
||||
(SGMiscd::normalizeAngle(min_hdg*SG_DEGREES_TO_RADIANS) +
|
||||
SGMiscd::normalizeAngle(max_hdg*SG_DEGREES_TO_RADIANS))/2);
|
||||
|
||||
double speed_fps = speed_kt * SG_NM_TO_METER * SG_METER_TO_FEET / 3600.0;
|
||||
_fromNorthNode->setDoubleValue( speed_fps * cos(avgHeading_rad) );
|
||||
_fromEastNode->setDoubleValue( speed_fps * sin(avgHeading_rad) );
|
||||
setOverride( true );
|
||||
}
|
||||
|
||||
Visibility::Visibility() :
|
||||
PresetBase("/environment/config/presets/visibility-m-override")
|
||||
{
|
||||
}
|
||||
|
||||
void Visibility::preset( double visibility_m )
|
||||
{
|
||||
// see: PresetBase::setOverride()
|
||||
if( _visibilityNode == NULL )
|
||||
_visibilityNode = fgGetNode("/environment/config/presets/visibility-m", true );
|
||||
|
||||
_visibilityNode->setDoubleValue(visibility_m );
|
||||
setOverride( true );
|
||||
}
|
||||
|
||||
Turbulence::Turbulence() :
|
||||
PresetBase("/environment/config/presets/turbulence-magnitude-norm-override")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void Turbulence::preset(double magnitude_norm)
|
||||
{
|
||||
// see: PresetBase::setOverride()
|
||||
if( _magnitudeNode == NULL )
|
||||
_magnitudeNode = fgGetNode("/environment/config/presets/turbulence-magnitude-norm", true );
|
||||
|
||||
_magnitudeNode->setDoubleValue( magnitude_norm );
|
||||
setOverride( true );
|
||||
}
|
||||
|
||||
Ceiling::Ceiling() :
|
||||
PresetBase("/environment/config/presets/ceiling-override")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void Ceiling::preset( double elevation, double thickness )
|
||||
{
|
||||
// see: PresetBase::setOverride()
|
||||
if( _elevationNode == NULL )
|
||||
_elevationNode = fgGetNode("/environment/config/presets/ceiling-elevation-ft", true);
|
||||
|
||||
if( _thicknessNode == NULL )
|
||||
_thicknessNode = fgGetNode("/environment/config/presets/ceiling-elevation-ft", true);
|
||||
|
||||
_elevationNode->setDoubleValue( elevation );
|
||||
_thicknessNode->setDoubleValue( thickness );
|
||||
setOverride( true );
|
||||
}
|
||||
|
||||
} // namespace Presets
|
||||
} // namespace Environment
|
||||
|
||||
94
src/Environment/presets.hxx
Normal file
94
src/Environment/presets.hxx
Normal file
@@ -0,0 +1,94 @@
|
||||
// presets.hxx -- Wrap environment presets
|
||||
//
|
||||
// Written by Torsten Dreyer, January 2011
|
||||
//
|
||||
// Copyright (C) 2010 Torsten Dreyer Torsten(at)t3r(dot)de
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef __ENVIRONMENT_PRESETS_HXX
|
||||
#define __ENVIRONMENT_PRESETS_HXX
|
||||
|
||||
#include <simgear/structure/Singleton.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
/**
|
||||
* @brief A wrapper for presets of environment properties
|
||||
* mainly set from the command line with --wind=270@10,
|
||||
* visibility=1600 etc.
|
||||
*/
|
||||
namespace Presets {
|
||||
|
||||
class PresetBase {
|
||||
public:
|
||||
PresetBase( const char * overrideNodePath );
|
||||
virtual void disablePreset() { setOverride(false); }
|
||||
protected:
|
||||
void setOverride( bool value );
|
||||
private:
|
||||
std::string _overrideNodePath;
|
||||
SGPropertyNode_ptr _overrideNode;
|
||||
};
|
||||
|
||||
class Ceiling : public PresetBase {
|
||||
public:
|
||||
Ceiling();
|
||||
void preset( double elevation, double thickness );
|
||||
private:
|
||||
SGPropertyNode_ptr _elevationNode;
|
||||
SGPropertyNode_ptr _thicknessNode;
|
||||
};
|
||||
|
||||
typedef simgear::Singleton<Ceiling> CeilingSingleton;
|
||||
|
||||
class Turbulence : public PresetBase {
|
||||
public:
|
||||
Turbulence();
|
||||
void preset( double magnitude_norm );
|
||||
private:
|
||||
SGPropertyNode_ptr _magnitudeNode;
|
||||
};
|
||||
|
||||
typedef simgear::Singleton<Turbulence> TurbulenceSingleton;
|
||||
|
||||
class Wind : public PresetBase {
|
||||
public:
|
||||
Wind();
|
||||
void preset( double min_hdg, double max_hdg, double speed, double gust );
|
||||
private:
|
||||
SGPropertyNode_ptr _fromNorthNode;
|
||||
SGPropertyNode_ptr _fromEastNode;
|
||||
};
|
||||
|
||||
typedef simgear::Singleton<Wind> WindSingleton;
|
||||
|
||||
class Visibility : public PresetBase {
|
||||
public:
|
||||
Visibility();
|
||||
void preset( double visibility_m );
|
||||
private:
|
||||
SGPropertyNode_ptr _visibilityNode;
|
||||
};
|
||||
|
||||
typedef simgear::Singleton<Visibility> VisibilitySingleton;
|
||||
|
||||
} // namespace Presets
|
||||
|
||||
} // namespace Environment
|
||||
|
||||
#endif //__ENVIRONMENT_PRESETS_HXX
|
||||
532
src/Environment/realwx_ctrl.cxx
Normal file
532
src/Environment/realwx_ctrl.cxx
Normal file
@@ -0,0 +1,532 @@
|
||||
// realwx_ctrl.cxx -- Process real weather data
|
||||
//
|
||||
// Written by David Megginson, started February 2002.
|
||||
// Rewritten by Torsten Dreyer, August 2010, August 2011
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "realwx_ctrl.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
#include <simgear/io/HTTPMemoryRequest.hxx>
|
||||
#include <simgear/timing/sg_time.hxx>
|
||||
#include <simgear/structure/event_mgr.hxx>
|
||||
#include <simgear/structure/commands.hxx>
|
||||
|
||||
#include "metarproperties.hxx"
|
||||
#include "metarairportfilter.hxx"
|
||||
#include "fgmetar.hxx"
|
||||
#include <Network/HTTPClient.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/sentryIntegration.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
class MetarRequester;
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
class LiveMetarProperties : public MetarProperties {
|
||||
public:
|
||||
LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge );
|
||||
virtual ~LiveMetarProperties();
|
||||
virtual void update( double dt );
|
||||
|
||||
virtual double getTimeToLive() const { return _timeToLive; }
|
||||
virtual void resetTimeToLive()
|
||||
{ _timeToLive = 0.00; _pollingTimer = 0.0; }
|
||||
|
||||
// implementation of MetarDataHandler
|
||||
virtual void handleMetarData( const std::string & data );
|
||||
virtual void handleMetarFailure();
|
||||
|
||||
static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
|
||||
static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
|
||||
|
||||
private:
|
||||
double _timeToLive;
|
||||
double _pollingTimer;
|
||||
MetarRequester * _metarRequester;
|
||||
int _maxAge;
|
||||
bool _failure;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
|
||||
|
||||
class MetarRequester {
|
||||
public:
|
||||
virtual void requestMetar( LiveMetarProperties_ptr metarDataHandler, const std::string & id ) = 0;
|
||||
};
|
||||
|
||||
|
||||
LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge ) :
|
||||
MetarProperties( rootNode ),
|
||||
_timeToLive(0.0),
|
||||
_pollingTimer(0.0),
|
||||
_metarRequester(metarRequester),
|
||||
_maxAge(maxAge),
|
||||
_failure(false)
|
||||
{
|
||||
_tiedProperties.Tie("time-to-live", &_timeToLive );
|
||||
_tiedProperties.Tie("failure", &_failure);
|
||||
}
|
||||
|
||||
LiveMetarProperties::~LiveMetarProperties()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
void LiveMetarProperties::update( double dt )
|
||||
{
|
||||
_timeToLive -= dt;
|
||||
_pollingTimer -= dt;
|
||||
if( _timeToLive <= 0.0 ) {
|
||||
_timeToLive = 0.0;
|
||||
invalidate();
|
||||
std::string stationId = getStationId();
|
||||
if( stationId.empty() ) return;
|
||||
if( _pollingTimer > 0.0 ) return;
|
||||
_metarRequester->requestMetar( this, stationId );
|
||||
_pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
void LiveMetarProperties::handleMetarData( const std::string & data )
|
||||
{
|
||||
SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
|
||||
_timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
|
||||
|
||||
SGSharedPtr<FGMetar> m;
|
||||
static bool haveReportedMETARFailure = false;
|
||||
try {
|
||||
m = new FGMetar(data.c_str());
|
||||
}
|
||||
catch( sg_io_exception &e) {
|
||||
SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << data <<
|
||||
" (" << e.getFormattedMessage() << ")");
|
||||
|
||||
// ensure we only report one METAR parse failure per session
|
||||
if (!haveReportedMETARFailure) {
|
||||
haveReportedMETARFailure = true;
|
||||
flightgear::sentryReportException("Failed to parse live METAR", data);
|
||||
}
|
||||
_failure = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_maxAge && (m->getAge_min() > _maxAge)) {
|
||||
// METAR is older than max-age, ignore
|
||||
SG_LOG( SG_ENVIRONMENT, SG_ALERT, "Ignoring outdated METAR for " << getStationId() << " (see /environment/params/metar-max-age-min)");
|
||||
return;
|
||||
}
|
||||
|
||||
_failure = false;
|
||||
setMetar( m );
|
||||
}
|
||||
|
||||
void LiveMetarProperties::handleMetarFailure()
|
||||
{
|
||||
_failure = true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
class BasicRealWxController : public RealWxController
|
||||
{
|
||||
public:
|
||||
BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
|
||||
virtual ~BasicRealWxController ();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void shutdown() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
/**
|
||||
* Create a metar-property binding at the specified property path,
|
||||
* and initiate a request for the specified station-ID (which may be
|
||||
* empty). If the property path is already mapped, the station ID
|
||||
* will be updated.
|
||||
*/
|
||||
void addMetarAtPath(const string& propPath, const string& icao);
|
||||
|
||||
void removeMetarAtPath(const string& propPath);
|
||||
|
||||
typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
|
||||
MetarPropertiesList::iterator findMetarAtPath(const string &propPath);
|
||||
|
||||
protected:
|
||||
void checkNearbyMetar();
|
||||
|
||||
long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
|
||||
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
SGPropertyNode_ptr _ground_elevation_n;
|
||||
SGPropertyNode_ptr _max_age_n;
|
||||
|
||||
bool _enabled;
|
||||
bool _wasEnabled;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
MetarPropertiesList _metarProperties;
|
||||
MetarRequester* _requester;
|
||||
};
|
||||
|
||||
static bool commandRequestMetar(const SGPropertyNode * arg, SGPropertyNode * root)
|
||||
{
|
||||
SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
|
||||
if (!envMgr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
|
||||
if (!self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string icao(arg->getStringValue("station"));
|
||||
std::transform(icao.begin(), icao.end(), icao.begin(), static_cast<int(*)(int)>(std::toupper));
|
||||
|
||||
string path = arg->getStringValue("path");
|
||||
self->addMetarAtPath(path, icao);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool commandClearMetar(const SGPropertyNode * arg, SGPropertyNode * root)
|
||||
{
|
||||
SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
|
||||
if (!envMgr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
|
||||
if (!self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string path = arg->getStringValue("path");
|
||||
self->removeMetarAtPath(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/*
|
||||
Properties
|
||||
~/enabled: bool Enables/Disables the realwx controller
|
||||
~/metar[1..n]: string Target property path for metar data
|
||||
*/
|
||||
|
||||
BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
|
||||
_rootNode(rootNode),
|
||||
_ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
|
||||
_max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
|
||||
_enabled(true),
|
||||
_wasEnabled(false),
|
||||
_requester(metarRequester)
|
||||
{
|
||||
|
||||
globals->get_commands()->addCommand("request-metar", commandRequestMetar);
|
||||
globals->get_commands()->addCommand("clear-metar", commandClearMetar);
|
||||
}
|
||||
|
||||
BasicRealWxController::~BasicRealWxController()
|
||||
{
|
||||
globals->get_commands()->removeCommand("request-metar");
|
||||
globals->get_commands()->removeCommand("clear-metar");
|
||||
}
|
||||
|
||||
void BasicRealWxController::init()
|
||||
{
|
||||
_wasEnabled = false;
|
||||
|
||||
// at least instantiate MetarProperties for /environment/metar
|
||||
SGPropertyNode_ptr metarNode = fgGetNode( _rootNode->getStringValue("metar", "/environment/metar"), true );
|
||||
_metarProperties.push_back( new LiveMetarProperties(metarNode,
|
||||
_requester,
|
||||
getMetarMaxAgeMin()));
|
||||
|
||||
for( auto n : _rootNode->getChildren("metar") ) {
|
||||
SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
|
||||
addMetarAtPath(metarNode->getPath(), "");
|
||||
}
|
||||
|
||||
checkNearbyMetar();
|
||||
update(0); // fetch data ASAP
|
||||
|
||||
globals->get_event_mgr()->addTask("checkNearbyMetar",
|
||||
[this](){ this->checkNearbyMetar(); }, 10 );
|
||||
}
|
||||
|
||||
void BasicRealWxController::reinit()
|
||||
{
|
||||
_wasEnabled = false;
|
||||
checkNearbyMetar();
|
||||
update(0); // fetch data ASAP
|
||||
}
|
||||
|
||||
void BasicRealWxController::shutdown()
|
||||
{
|
||||
globals->get_event_mgr()->removeTask("checkNearbyMetar");
|
||||
}
|
||||
|
||||
void BasicRealWxController::bind()
|
||||
{
|
||||
_tiedProperties.setRoot( _rootNode );
|
||||
_tiedProperties.Tie( "enabled", &_enabled );
|
||||
}
|
||||
|
||||
void BasicRealWxController::unbind()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
void BasicRealWxController::update( double dt )
|
||||
{
|
||||
if( _enabled ) {
|
||||
bool firstIteration = !_wasEnabled;
|
||||
// clock tick for every METAR in stock
|
||||
for(auto p : _metarProperties) {
|
||||
// first round? All received METARs are outdated
|
||||
if( firstIteration ) p->resetTimeToLive();
|
||||
p->update(dt);
|
||||
}
|
||||
|
||||
_wasEnabled = true;
|
||||
} else {
|
||||
_wasEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
|
||||
{
|
||||
// check for duplicate entries
|
||||
MetarPropertiesList::iterator it = findMetarAtPath(propPath);
|
||||
if( it != _metarProperties.end() ) {
|
||||
SG_LOG( SG_ENVIRONMENT, SG_INFO, "Reusing metar properties at " << propPath << " for " << icao);
|
||||
// already exists
|
||||
if ((*it)->getStationId() != icao) {
|
||||
(*it)->setStationId(icao);
|
||||
(*it)->resetTimeToLive();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
|
||||
SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath << " for " << icao);
|
||||
LiveMetarProperties_ptr p(new LiveMetarProperties( metarNode, _requester, getMetarMaxAgeMin() ));
|
||||
_metarProperties.push_back(p);
|
||||
p->setStationId(icao);
|
||||
}
|
||||
|
||||
void BasicRealWxController::removeMetarAtPath(const string &propPath)
|
||||
{
|
||||
MetarPropertiesList::iterator it = findMetarAtPath( propPath );
|
||||
if( it != _metarProperties.end() ) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "removing metar properties at " << propPath);
|
||||
_metarProperties.erase(it);
|
||||
} else {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
|
||||
}
|
||||
}
|
||||
|
||||
BasicRealWxController::MetarPropertiesList::iterator BasicRealWxController::findMetarAtPath(const string &propPath)
|
||||
{
|
||||
// don not compare unprocessed property path
|
||||
// /foo/bar[0]/baz equals /foo/bar/baz
|
||||
SGPropertyNode_ptr n = fgGetNode(propPath,false);
|
||||
if( !n.valid() ) // trivial: node does not exist
|
||||
return _metarProperties.end();
|
||||
|
||||
MetarPropertiesList::iterator it = _metarProperties.begin();
|
||||
while( it != _metarProperties.end() &&
|
||||
(*it)->get_root_node()->getPath() != n->getPath() )
|
||||
++it;
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
void BasicRealWxController::checkNearbyMetar()
|
||||
{
|
||||
try {
|
||||
const SGGeod & pos = globals->get_aircraft_position();
|
||||
|
||||
// check nearest airport
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
|
||||
|
||||
FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
|
||||
if( nearestAirport == NULL ) {
|
||||
SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_ENVIRONMENT, SG_DEBUG,
|
||||
"NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
|
||||
|
||||
// if it has changed, invalidate the associated METAR
|
||||
if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO,
|
||||
"NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
|
||||
_metarProperties[0]->getStationId() <<
|
||||
"', new: '" << nearestAirport->ident() << "'" );
|
||||
_metarProperties[0]->setStationId( nearestAirport->ident() );
|
||||
_metarProperties[0]->resetTimeToLive();
|
||||
}
|
||||
}
|
||||
catch( sg_exception & ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester
|
||||
{
|
||||
public:
|
||||
NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
|
||||
|
||||
// implementation of MetarRequester
|
||||
virtual void requestMetar( LiveMetarProperties_ptr metarDataHandler, const std::string & id );
|
||||
|
||||
virtual ~NoaaMetarRealWxController()
|
||||
{
|
||||
}
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "noaa-metar-real-wx-controller"; }
|
||||
|
||||
private:
|
||||
std::string noaa_base_url;
|
||||
};
|
||||
|
||||
NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
|
||||
BasicRealWxController(rootNode, this )
|
||||
{
|
||||
// default to hardcoded URL for compatibility
|
||||
noaa_base_url = "https://tgftp.nws.noaa.gov/data/observations/metar/stations/[station].TXT";
|
||||
|
||||
// override with environment/realwx/metar-url (if present)
|
||||
SGPropertyNode *urlNode = _rootNode->getNode("metar-url", false);
|
||||
if (urlNode != nullptr)
|
||||
noaa_base_url = urlNode->getStringValue();
|
||||
}
|
||||
|
||||
void NoaaMetarRealWxController::requestMetar
|
||||
(
|
||||
LiveMetarProperties_ptr metarDataHandler,
|
||||
const std::string& id
|
||||
)
|
||||
{
|
||||
class NoaaMetarGetRequest:
|
||||
public simgear::HTTP::MemoryRequest
|
||||
{
|
||||
public:
|
||||
NoaaMetarGetRequest( LiveMetarProperties_ptr metarDataHandler,
|
||||
const std::string& stationId,
|
||||
const std::string &base_url):
|
||||
MemoryRequest( simgear::strutils::replace(base_url, "[station]",stationId) ),
|
||||
_metarDataHandler(metarDataHandler)
|
||||
{
|
||||
std::ostringstream buf;
|
||||
buf << globals->get_time_params()->get_cur_time();
|
||||
requestHeader("X-TIME") = buf.str();
|
||||
}
|
||||
|
||||
virtual void onDone()
|
||||
{
|
||||
if( responseCode() != 200 )
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_ENVIRONMENT,
|
||||
SG_WARN,
|
||||
"metar download failed:" << url() << ": reason:" << responseReason()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_metarDataHandler->handleMetarData
|
||||
(
|
||||
simgear::strutils::simplify(responseBody())
|
||||
);
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
|
||||
_metarDataHandler->handleMetarFailure();
|
||||
}
|
||||
|
||||
private:
|
||||
LiveMetarProperties_ptr _metarDataHandler;
|
||||
};
|
||||
|
||||
string upperId = id;
|
||||
std::transform(upperId.begin(), upperId.end(), upperId.begin(), static_cast<int(*)(int)>(std::toupper));
|
||||
|
||||
SG_LOG
|
||||
(
|
||||
SG_ENVIRONMENT,
|
||||
SG_INFO,
|
||||
"NoaaMetarRealWxController::update(): "
|
||||
"spawning load request for station-id '" << upperId << "'"
|
||||
);
|
||||
FGHTTPClient* http = globals->get_subsystem<FGHTTPClient>();
|
||||
if (http) {
|
||||
http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId, noaa_base_url));
|
||||
}
|
||||
}
|
||||
|
||||
// Register the subsystem.
|
||||
#if 0
|
||||
SGSubsystemMgr::Registrant<NoaaMetarRealWxController> registrantNoaaMetarRealWxController(
|
||||
SGSubsystemMgr::GENERAL,
|
||||
{{"environment", SGSubsystemMgr::Dependency::SOFT},
|
||||
{"FGHTTPClient", SGSubsystemMgr::Dependency::SOFT},
|
||||
{"realwx", SGSubsystemMgr::Dependency::SOFT}});
|
||||
#endif
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
|
||||
{
|
||||
return new NoaaMetarRealWxController( rootNode );
|
||||
}
|
||||
|
||||
RealWxController::~RealWxController()
|
||||
{
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
} // namespace Environment
|
||||
40
src/Environment/realwx_ctrl.hxx
Normal file
40
src/Environment/realwx_ctrl.hxx
Normal file
@@ -0,0 +1,40 @@
|
||||
// realwx_ctrl.cxx -- Process real weather data
|
||||
//
|
||||
// Written by David Megginson, started May 2002.
|
||||
// Rewritten by Torsten Dreyer, August 2010
|
||||
//
|
||||
// Copyright (C) 2002 David Megginson - david@megginson.com
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef _REALWX_CTRL_HXX
|
||||
#define _REALWX_CTRL_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
class RealWxController : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
virtual ~RealWxController();
|
||||
|
||||
static RealWxController * createInstance( SGPropertyNode_ptr rootNode );
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif // _REALWX_CTRL_HXX
|
||||
216
src/Environment/ridge_lift.cxx
Normal file
216
src/Environment/ridge_lift.cxx
Normal file
@@ -0,0 +1,216 @@
|
||||
// simulates ridge lift
|
||||
//
|
||||
// Written by Patrice Poly
|
||||
// Copyright (C) 2009 Patrice Poly - p.polypa@gmail.com
|
||||
//
|
||||
//
|
||||
// Entirely based on the paper :
|
||||
// http://carrier.csi.cam.ac.uk/forsterlewis/soaring/sim/fsx/dev/sim_probe/sim_probe_paper.html
|
||||
// by Ian Forster-Lewis, University of Cambridge, 26th December 2007
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include <Main/util.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <simgear/sg_inlines.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
#include "ridge_lift.hxx"
|
||||
|
||||
static const double BOUNDARY1_m = 40.0;
|
||||
|
||||
const double FGRidgeLift::dist_probe_m[] = { // in meters
|
||||
0.0,
|
||||
250.0,
|
||||
750.0,
|
||||
2000.0,
|
||||
-100.0
|
||||
};
|
||||
|
||||
//constructor
|
||||
FGRidgeLift::FGRidgeLift () :
|
||||
lift_factor(0.0)
|
||||
{
|
||||
strength = 0.0;
|
||||
timer = 0.0;
|
||||
for( int i = 0; i < 5; i++ )
|
||||
probe_elev_m[i] = probe_lat_deg[i] = probe_lon_deg[i] = 0.0;
|
||||
|
||||
for( int i = 0; i < 4; i++ )
|
||||
slope[i] = 0.0;
|
||||
}
|
||||
|
||||
//destructor
|
||||
FGRidgeLift::~FGRidgeLift()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FGRidgeLift::init(void)
|
||||
{
|
||||
_enabled_node = fgGetNode( "/environment/ridge-lift/enabled", false );
|
||||
|
||||
_ridge_lift_fps_node = fgGetNode("/environment/ridge-lift-fps", true);
|
||||
_surface_wind_from_deg_node =
|
||||
fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg"
|
||||
, true);
|
||||
_surface_wind_speed_node =
|
||||
fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt"
|
||||
, true);
|
||||
_user_longitude_node = fgGetNode("/position/longitude-deg", true);
|
||||
_user_latitude_node = fgGetNode("/position/latitude-deg", true);
|
||||
_user_altitude_agl_ft_node = fgGetNode("/position/altitude-agl-ft", true);
|
||||
_ground_elev_node = fgGetNode("/position/ground-elev-ft", true );
|
||||
}
|
||||
|
||||
void FGRidgeLift::bind() {
|
||||
string prop;
|
||||
|
||||
_tiedProperties.setRoot( fgGetNode("/environment/ridge-lift",true));
|
||||
for( int i = 0; i < 5; i++ ) {
|
||||
_tiedProperties.Tie( "probe-elev-m", i, this, i, &FGRidgeLift::get_probe_elev_m );
|
||||
_tiedProperties.Tie( "probe-lat-deg", i, this, i, &FGRidgeLift::get_probe_lat_deg );
|
||||
_tiedProperties.Tie( "probe-lon-deg", i, this, i, &FGRidgeLift::get_probe_lon_deg );
|
||||
}
|
||||
|
||||
for( int i = 0; i < 4; i++ ) {
|
||||
_tiedProperties.Tie( "slope", i, this, i, &FGRidgeLift::get_slope );
|
||||
}
|
||||
}
|
||||
|
||||
void FGRidgeLift::unbind() {
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
void FGRidgeLift::update(double dt) {
|
||||
|
||||
if( dt <= SGLimitsd::min() ) // paused, do nothing but keep current lift
|
||||
return;
|
||||
|
||||
if( _enabled_node && !_enabled_node->getBoolValue() ) {
|
||||
// do nothing if lift has been zeroed
|
||||
if( strength != 0.0 ) {
|
||||
if( strength > 0.1 ) {
|
||||
// slowly fade out strong lifts
|
||||
strength = fgGetLowPass( strength, 0, dt );
|
||||
} else {
|
||||
strength = 0.0;
|
||||
}
|
||||
_ridge_lift_fps_node->setDoubleValue( strength );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
timer -= dt;
|
||||
if (timer <= 0.0 ) {
|
||||
|
||||
// probe0 is current position
|
||||
probe_lat_deg[0] = _user_latitude_node->getDoubleValue();
|
||||
probe_lon_deg[0] = _user_longitude_node->getDoubleValue();
|
||||
probe_elev_m[0] = _ground_elev_node->getDoubleValue() * SG_FEET_TO_METER;
|
||||
|
||||
// position is geodetic, need geocentric for advanceRadM
|
||||
SGGeod myGeodPos = SGGeod::fromDegM( probe_lon_deg[0], probe_lat_deg[0], 20000.0 );
|
||||
SGGeoc myGeocPos = SGGeoc::fromGeod( myGeodPos );
|
||||
double ground_wind_from_rad = _surface_wind_from_deg_node->getDoubleValue() * SG_DEGREES_TO_RADIANS;
|
||||
|
||||
// compute the remaining probes
|
||||
for (unsigned i = 1; i < sizeof(probe_elev_m)/sizeof(probe_elev_m[0]); i++) {
|
||||
SGGeoc probe = myGeocPos.advanceRadM( ground_wind_from_rad, dist_probe_m[i] );
|
||||
// convert to geodetic position for ground level computation
|
||||
SGGeod probeGeod = SGGeod::fromGeoc( probe );
|
||||
probe_lat_deg[i] = probeGeod.getLatitudeDeg();
|
||||
probe_lon_deg[i] = probeGeod.getLongitudeDeg();
|
||||
if (!globals->get_scenery()->get_elevation_m( probeGeod, probe_elev_m[i], NULL )) {
|
||||
// no ground found? use elevation of previous probe :-(
|
||||
probe_elev_m[i] = probe_elev_m[i-1];
|
||||
}
|
||||
}
|
||||
|
||||
// slopes
|
||||
double adj_slope[sizeof(slope)];
|
||||
slope[0] = (probe_elev_m[0] - probe_elev_m[1]) / dist_probe_m[1];
|
||||
slope[1] = (probe_elev_m[1] - probe_elev_m[2]) / dist_probe_m[2];
|
||||
slope[2] = (probe_elev_m[2] - probe_elev_m[3]) / dist_probe_m[3];
|
||||
slope[3] = (probe_elev_m[4] - probe_elev_m[0]) / -dist_probe_m[4];
|
||||
|
||||
for (unsigned i = 0; i < sizeof(slope)/sizeof(slope[0]); i++)
|
||||
adj_slope[i] = sin(atan(5.0 * pow ( (fabs(slope[i])),1.7) ) ) *SG_SIGN<double>(slope[i]);
|
||||
|
||||
//adjustment
|
||||
adj_slope[0] *= 0.2;
|
||||
adj_slope[1] *= 0.2;
|
||||
if ( adj_slope [2] < 0.0 ) {
|
||||
adj_slope[2] *= 0.5;
|
||||
} else {
|
||||
adj_slope[2] = 0.0 ;
|
||||
}
|
||||
|
||||
if ( ( adj_slope [0] >= 0.0 ) && ( adj_slope [3] < 0.0 ) ) {
|
||||
adj_slope[3] = 0.0;
|
||||
} else {
|
||||
adj_slope[3] *= 0.2;
|
||||
}
|
||||
lift_factor = adj_slope[0]+adj_slope[1]+adj_slope[2]+adj_slope[3];
|
||||
|
||||
// restart the timer
|
||||
timer = 1.0;
|
||||
}
|
||||
|
||||
//user altitude above ground
|
||||
double user_altitude_agl_m = _user_altitude_agl_ft_node->getDoubleValue() * SG_FEET_TO_METER;
|
||||
|
||||
//boundaries
|
||||
double boundary2_m = 130.0; // in the lift
|
||||
if (lift_factor < 0.0) { // in the sink
|
||||
double highest_probe_temp= std::max ( probe_elev_m[1], probe_elev_m[2] );
|
||||
double highest_probe_downwind_m= std::max ( highest_probe_temp, probe_elev_m[3] );
|
||||
boundary2_m = highest_probe_downwind_m - probe_elev_m[0];
|
||||
}
|
||||
|
||||
double agl_factor;
|
||||
if ( user_altitude_agl_m < BOUNDARY1_m ) {
|
||||
agl_factor = 0.5+0.5*user_altitude_agl_m /BOUNDARY1_m ;
|
||||
} else if ( user_altitude_agl_m < boundary2_m ) {
|
||||
agl_factor = 1.0;
|
||||
} else {
|
||||
agl_factor = exp(-(2 + probe_elev_m[0] / 2000) *
|
||||
(user_altitude_agl_m - boundary2_m) / std::max(probe_elev_m[0],200.0));
|
||||
}
|
||||
|
||||
double ground_wind_speed_mps = _surface_wind_speed_node->getDoubleValue() * SG_NM_TO_METER / 3600;
|
||||
double lift_mps = lift_factor* ground_wind_speed_mps * agl_factor;
|
||||
|
||||
//the updraft, finally, in ft per second
|
||||
strength = fgGetLowPass( strength, lift_mps * SG_METER_TO_FEET, dt );
|
||||
_ridge_lift_fps_node->setDoubleValue( strength );
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
SGSubsystemMgr::Registrant<FGRidgeLift> registrantFGRidgeLift;
|
||||
82
src/Environment/ridge_lift.hxx
Normal file
82
src/Environment/ridge_lift.hxx
Normal file
@@ -0,0 +1,82 @@
|
||||
// simulates ridge lift
|
||||
//
|
||||
// Written by Patrice Poly
|
||||
// Copyright (C) 2009 Patrice Poly - p.polypa@gmail.com
|
||||
//
|
||||
//
|
||||
// Entirely based on the paper :
|
||||
// http://carrier.csi.cam.ac.uk/forsterlewis/soaring/sim/fsx/dev/sim_probe/sim_probe_paper.html
|
||||
// by Ian Forster-Lewis, University of Cambridge, 26th December 2007
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
class FGRidgeLift : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
FGRidgeLift();
|
||||
~FGRidgeLift();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "ridgelift"; }
|
||||
|
||||
inline double getStrength() const { return strength; };
|
||||
|
||||
inline double get_probe_elev_m( int index ) const { return probe_elev_m[index]; };
|
||||
inline double get_probe_lat_deg( int index ) const { return probe_lat_deg[index]; };
|
||||
inline double get_probe_lon_deg( int index ) const { return probe_lon_deg[index]; };
|
||||
inline double get_slope( int index ) const { return slope[index]; };
|
||||
|
||||
private:
|
||||
static const double dist_probe_m[5];
|
||||
|
||||
double strength;
|
||||
double timer;
|
||||
|
||||
double probe_lat_deg[5];
|
||||
double probe_lon_deg[5];
|
||||
double probe_elev_m[5];
|
||||
|
||||
double slope[4];
|
||||
|
||||
double lift_factor;
|
||||
|
||||
SGPropertyNode_ptr _enabled_node;
|
||||
SGPropertyNode_ptr _ridge_lift_fps_node;
|
||||
|
||||
SGPropertyNode_ptr _surface_wind_from_deg_node;
|
||||
SGPropertyNode_ptr _surface_wind_speed_node;
|
||||
|
||||
SGPropertyNode_ptr _user_altitude_agl_ft_node;
|
||||
SGPropertyNode_ptr _user_longitude_node;
|
||||
SGPropertyNode_ptr _user_latitude_node;
|
||||
SGPropertyNode_ptr _ground_elev_node;
|
||||
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
};
|
||||
434
src/Environment/terrainsampler.cxx
Normal file
434
src/Environment/terrainsampler.cxx
Normal file
@@ -0,0 +1,434 @@
|
||||
// terrainsampler.cxx --
|
||||
//
|
||||
// Written by Torsten Dreyer, started July 2010
|
||||
// Based on local weather implementation in nasal from
|
||||
// Thorsten Renk
|
||||
//
|
||||
// Copyright (C) 2010 Curtis Olson
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <simgear/math/sg_random.hxx>
|
||||
#include <Scenery/scenery.hxx>
|
||||
#include <deque>
|
||||
|
||||
#include "terrainsampler.hxx"
|
||||
|
||||
using simgear::PropertyList;
|
||||
using std::deque;
|
||||
using std::vector;
|
||||
using std::ostringstream;
|
||||
using std::string;
|
||||
|
||||
#include <simgear/props/tiedpropertylist.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
/**
|
||||
* @brief Class for presampling the terrain roughness
|
||||
*/
|
||||
class AreaSampler : public SGSubsystem
|
||||
{
|
||||
public:
|
||||
AreaSampler( SGPropertyNode_ptr rootNode );
|
||||
virtual ~AreaSampler();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
void init() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double dt) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "area"; }
|
||||
|
||||
int getElevationHistogramStep() const { return _elevationHistogramStep; }
|
||||
void setElevationHistograpStep( int value ) {
|
||||
_elevationHistogramStep = value > 0 ? value : 500;
|
||||
_elevationHistogramCount = _elevationHistogramMax / _elevationHistogramStep;
|
||||
}
|
||||
|
||||
int getElevationHistogramMax() const { return _elevationHistogramMax; }
|
||||
void setElevationHistograpMax( int value ) {
|
||||
_elevationHistogramMax = value > 0 ? value : 10000;
|
||||
_elevationHistogramCount = _elevationHistogramMax / _elevationHistogramStep;
|
||||
}
|
||||
|
||||
int getElevationHistogramCount() const { return _elevationHistogramCount; }
|
||||
|
||||
private:
|
||||
void analyse();
|
||||
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
|
||||
bool _enabled;
|
||||
bool _useAircraftPosition;
|
||||
double _heading_deg;
|
||||
double _speed_kt;
|
||||
int _radius;
|
||||
double _max_computation_time_norm;
|
||||
int _max_samples; // keep xx samples in queue for analysis
|
||||
double _reuse_samples_norm;
|
||||
double _recalc_distance_norm;
|
||||
int _elevationHistogramMax;
|
||||
int _elevationHistogramStep;
|
||||
int _elevationHistogramCount;
|
||||
SGGeod _inputPosition;
|
||||
|
||||
double _altOffset;
|
||||
double _altMedian;
|
||||
double _altMin;
|
||||
double _altLayered;
|
||||
double _altMean;
|
||||
SGGeod _outputPosition;
|
||||
|
||||
SGPropertyNode_ptr _signalNode;
|
||||
SGPropertyNode_ptr _positionLatitudeNode;
|
||||
SGPropertyNode_ptr _positionLongitudeNode;
|
||||
|
||||
deque<double> _elevations;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
};
|
||||
|
||||
AreaSampler::AreaSampler( SGPropertyNode_ptr rootNode ) :
|
||||
_rootNode(rootNode),
|
||||
_enabled(true),
|
||||
_useAircraftPosition(false),
|
||||
_heading_deg(0.0),
|
||||
_speed_kt(0.0),
|
||||
_radius(40000.0),
|
||||
_max_computation_time_norm(0.1),
|
||||
_max_samples(1000),
|
||||
_reuse_samples_norm(0.8),
|
||||
_recalc_distance_norm(0.1),
|
||||
_elevationHistogramMax(10000),
|
||||
_elevationHistogramStep(500),
|
||||
_elevationHistogramCount(_elevationHistogramMax/_elevationHistogramStep),
|
||||
_altOffset(0),
|
||||
_altMedian(0),
|
||||
_altMin(0),
|
||||
_altLayered(0),
|
||||
_altMean(0),
|
||||
_signalNode(rootNode->getNode("output/valid", true )),
|
||||
_positionLatitudeNode(fgGetNode( "/position/latitude-deg", true )),
|
||||
_positionLongitudeNode(fgGetNode( "/position/longitude-deg", true ))
|
||||
{
|
||||
_inputPosition.setElevationM( SG_MAX_ELEVATION_M );
|
||||
}
|
||||
|
||||
AreaSampler::~AreaSampler()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void AreaSampler::bind()
|
||||
{
|
||||
_tiedProperties.setRoot( _rootNode );
|
||||
_tiedProperties.Tie( "enabled", &_enabled );
|
||||
|
||||
_tiedProperties.setRoot( _rootNode->getNode( "input", true ) );
|
||||
_tiedProperties.Tie( "use-aircraft-position", &_useAircraftPosition );
|
||||
_tiedProperties.Tie( "latitude-deg", &_inputPosition, &SGGeod::getLatitudeDeg, &SGGeod::setLatitudeDeg );
|
||||
_tiedProperties.Tie( "longitude-deg", &_inputPosition, &SGGeod::getLongitudeDeg, &SGGeod::setLongitudeDeg );
|
||||
_tiedProperties.Tie( "heading-deg", &_heading_deg );
|
||||
_tiedProperties.Tie( "speed-kt", &_speed_kt );
|
||||
_tiedProperties.Tie( "radius-m", &_radius );
|
||||
_tiedProperties.Tie( "max-computation-time-norm", &_max_computation_time_norm );
|
||||
_tiedProperties.Tie( "max-samples", &_max_samples );
|
||||
_tiedProperties.Tie( "reuse-samples-norm", &_reuse_samples_norm );
|
||||
_tiedProperties.Tie( "recalc-distance-norm", &_recalc_distance_norm );
|
||||
_tiedProperties.Tie( "elevation-histogram-max-ft", this, &AreaSampler::getElevationHistogramMax, &AreaSampler::setElevationHistograpMax );
|
||||
_tiedProperties.Tie( "elevation-histogram-step-ft", this, &AreaSampler::getElevationHistogramStep, &AreaSampler::setElevationHistograpStep );
|
||||
_tiedProperties.Tie( "elevation-histogram-count", this, &AreaSampler::getElevationHistogramCount );
|
||||
|
||||
_tiedProperties.setRoot( _rootNode->getNode( "output", true ) );
|
||||
_tiedProperties.Tie( "alt-offset-ft", &_altOffset );
|
||||
_tiedProperties.Tie( "alt-median-ft", &_altMedian );
|
||||
_tiedProperties.Tie( "alt-min-ft", &_altMin );
|
||||
_tiedProperties.Tie( "alt-layered-ft", &_altLayered );
|
||||
_tiedProperties.Tie( "alt-mean-ft", &_altMean );
|
||||
_tiedProperties.Tie( "longitude-deg", &_outputPosition, &SGGeod::getLongitudeDeg );
|
||||
_tiedProperties.Tie( "latitude-deg", &_outputPosition, &SGGeod::getLatitudeDeg );
|
||||
|
||||
}
|
||||
|
||||
void AreaSampler::unbind()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
}
|
||||
|
||||
void AreaSampler::init()
|
||||
{
|
||||
_signalNode->setBoolValue(false);
|
||||
_elevations.clear();
|
||||
_altOffset = 0.0;
|
||||
_altMedian = 0.0;
|
||||
_altMin = 0.0;
|
||||
_altLayered = 0.0;
|
||||
_altMean = 0.0;
|
||||
}
|
||||
|
||||
void AreaSampler::reinit()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void AreaSampler::update( double dt )
|
||||
{
|
||||
// if not enabled or time has stalled, do nothing
|
||||
if( !(_enabled && dt > SGLimitsd::min()) )
|
||||
return;
|
||||
|
||||
// get the aircraft's position if requested
|
||||
if( _useAircraftPosition && _speed_kt < 0.5 ) {
|
||||
_inputPosition = SGGeod::fromDegM(
|
||||
_positionLongitudeNode->getDoubleValue(),
|
||||
_positionLatitudeNode->getDoubleValue(),
|
||||
SG_MAX_ELEVATION_M );
|
||||
}
|
||||
|
||||
// need geocentric coordinates
|
||||
SGGeoc center = SGGeoc::fromGeod( _inputPosition );
|
||||
|
||||
// if a speed is set, move the input position
|
||||
if( _speed_kt >= 0.5 ) {
|
||||
double distance_m = _speed_kt * dt * SG_NM_TO_METER;
|
||||
center = center.advanceRadM( _heading_deg * SG_DEGREES_TO_RADIANS, distance_m );
|
||||
_inputPosition = SGGeod::fromGeoc( center );
|
||||
}
|
||||
|
||||
if( _signalNode->getBoolValue() ) {
|
||||
// if we had finished the iteration and moved more than 10% of the radius
|
||||
// of the sampling area, drop the oldest samples and continue sampling
|
||||
if( SGGeoc::distanceM( center, SGGeoc::fromGeod(_outputPosition ) ) >= _recalc_distance_norm * _radius ) {
|
||||
_elevations.resize( _max_samples * _reuse_samples_norm );
|
||||
_signalNode->setBoolValue( false );
|
||||
}
|
||||
}
|
||||
|
||||
if( _signalNode->getBoolValue() )
|
||||
return; // nothing to do.
|
||||
|
||||
FGScenery * scenery = globals->get_scenery();
|
||||
|
||||
SGTimeStamp start = SGTimeStamp::now();
|
||||
while( (SGTimeStamp::now() - start).toSecs() < dt * _max_computation_time_norm ) {
|
||||
// sample until we used up all our configured time
|
||||
double distance = sg_random();
|
||||
distance = _radius * (1-distance*distance);
|
||||
double course = sg_random() * 2.0 * SG_PI;
|
||||
SGGeod probe = SGGeod::fromGeoc(center.advanceRadM( course, distance ));
|
||||
double elevation_m = 0.0;
|
||||
|
||||
if (scenery->get_elevation_m( probe, elevation_m, NULL ))
|
||||
_elevations.push_front(elevation_m *= SG_METER_TO_FEET);
|
||||
|
||||
if( _elevations.size() >= (deque<unsigned>::size_type)_max_samples ) {
|
||||
// sampling complete?
|
||||
analyse();
|
||||
_outputPosition = _inputPosition;
|
||||
_signalNode->setBoolValue( true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AreaSampler::analyse()
|
||||
{
|
||||
double sum;
|
||||
|
||||
vector<int> histogram(_elevationHistogramCount,0);
|
||||
|
||||
for( deque<double>::size_type i = 0; i < _elevations.size(); i++ ) {
|
||||
int idx = SGMisc<int>::clip( (int)(_elevations[i]/_elevationHistogramStep), 0, histogram.size()-1 );
|
||||
histogram[idx]++;
|
||||
}
|
||||
|
||||
_altMedian = 0.0;
|
||||
sum = 0.0;
|
||||
for( vector<int>::size_type i = 0; i < histogram.size(); i++ ) {
|
||||
sum += histogram[i];
|
||||
if( sum > 0.5 * _elevations.size() ) {
|
||||
_altMedian = i * _elevationHistogramStep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_altOffset = 0.0;
|
||||
sum = 0.0;
|
||||
for( vector<int>::size_type i = 0; i < histogram.size(); i++ ) {
|
||||
sum += histogram[i];
|
||||
if( sum > 0.3 * _elevations.size() ) {
|
||||
_altOffset = i * _elevationHistogramStep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_altMean = 0.0;
|
||||
for( vector<int>::size_type i = 0; i < histogram.size(); i++ ) {
|
||||
_altMean += histogram[i] * i;
|
||||
}
|
||||
_altMean *= _elevationHistogramStep;
|
||||
if( _elevations.size() != 0.0 ) _altMean /= _elevations.size();
|
||||
|
||||
_altMin = 0.0;
|
||||
for( vector<int>::size_type i = 0; i < histogram.size(); i++ ) {
|
||||
if( histogram[i] > 0 ) {
|
||||
_altMin = i * _elevationHistogramStep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
double alt_low_min = 0.0;
|
||||
double n_max = 0.0;
|
||||
sum = 0.0;
|
||||
for( vector<int>::size_type i = 0; i < histogram.size()-1; i++ ) {
|
||||
sum += histogram[i];
|
||||
if( histogram[i] > n_max ) n_max = histogram[i];
|
||||
if( n_max > histogram[i+1] && sum > 0.3*_elevations.size()) {
|
||||
alt_low_min = i * _elevationHistogramStep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
_altLayered = 0.5 * (_altMin + _altOffset);
|
||||
|
||||
#if 0
|
||||
append(alt_50_array, alt_med);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Register the subsystem.
|
||||
#if 0
|
||||
SGSubsystemMgr::Registrant<AreaSampler> registrantAreaSampler;
|
||||
#endif
|
||||
|
||||
/* --------------------- End of AreaSampler implementation ------------- */
|
||||
|
||||
/* --------------------- TerrainSamplerImplementation -------------------------- */
|
||||
|
||||
class TerrainSamplerImplementation : public TerrainSampler
|
||||
{
|
||||
public:
|
||||
TerrainSamplerImplementation ( SGPropertyNode_ptr rootNode );
|
||||
virtual ~TerrainSamplerImplementation ();
|
||||
|
||||
// Subsystem API.
|
||||
void bind() override;
|
||||
InitStatus incrementalInit() override;
|
||||
void init() override;
|
||||
void postinit() override;
|
||||
void reinit() override;
|
||||
void unbind() override;
|
||||
void update(double delta_time_sec) override;
|
||||
|
||||
// Subsystem identification.
|
||||
static const char* staticSubsystemClassId() { return "terrain-sampler"; }
|
||||
|
||||
private:
|
||||
inline string areaSubsystemName( unsigned i ) {
|
||||
ostringstream name;
|
||||
name << "area" << i;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr _rootNode;
|
||||
bool _enabled;
|
||||
simgear::TiedPropertyList _tiedProperties;
|
||||
};
|
||||
|
||||
TerrainSamplerImplementation::TerrainSamplerImplementation( SGPropertyNode_ptr rootNode ) :
|
||||
_rootNode( rootNode ),
|
||||
_enabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
TerrainSamplerImplementation::~TerrainSamplerImplementation()
|
||||
{
|
||||
}
|
||||
|
||||
SGSubsystem::InitStatus TerrainSamplerImplementation::incrementalInit()
|
||||
{
|
||||
init();
|
||||
return INIT_DONE;
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::init()
|
||||
{
|
||||
PropertyList areaNodes = _rootNode->getChildren( "area" );
|
||||
|
||||
for( PropertyList::size_type i = 0; i < areaNodes.size(); i++ )
|
||||
set_subsystem( areaSubsystemName(i), new AreaSampler( areaNodes[i] ) );
|
||||
|
||||
SGSubsystemGroup::init();
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::postinit()
|
||||
{
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::reinit()
|
||||
{
|
||||
for( unsigned i = 0;; i++ ) {
|
||||
string subsystemName = areaSubsystemName(i);
|
||||
SGSubsystem * subsys = get_subsystem( subsystemName );
|
||||
if( subsys == NULL )
|
||||
break;
|
||||
remove_subsystem( subsystemName );
|
||||
subsys->unbind();
|
||||
delete subsys;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::bind()
|
||||
{
|
||||
SGSubsystemGroup::bind();
|
||||
_tiedProperties.Tie( _rootNode->getNode("enabled",true), &_enabled );
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::unbind()
|
||||
{
|
||||
_tiedProperties.Untie();
|
||||
SGSubsystemGroup::unbind();
|
||||
}
|
||||
|
||||
void TerrainSamplerImplementation::update( double dt )
|
||||
{
|
||||
if( !(_enabled && dt > SGLimitsd::min()) )
|
||||
return;
|
||||
SGSubsystemGroup::update(dt);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* implementation of the TerrainSampler factory to hide the implementation
|
||||
details */
|
||||
TerrainSampler * TerrainSampler::createInstance( SGPropertyNode_ptr rootNode )
|
||||
{
|
||||
return new TerrainSamplerImplementation( rootNode );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
37
src/Environment/terrainsampler.hxx
Normal file
37
src/Environment/terrainsampler.hxx
Normal file
@@ -0,0 +1,37 @@
|
||||
// terrainsampler.hxx --
|
||||
//
|
||||
// Written by Torsten Dreyer, started July 2010
|
||||
// Based on local weather implementation in nasal from
|
||||
// Thorsten Renk
|
||||
//
|
||||
// Copyright (C) 2010 Curtis Olson
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
#ifndef _TERRAIN_SAMPLER_HXX
|
||||
#define _TERRAIN_SAMPLER_HXX
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
namespace Environment {
|
||||
|
||||
class TerrainSampler : public SGSubsystemGroup
|
||||
{
|
||||
public:
|
||||
static TerrainSampler * createInstance( SGPropertyNode_ptr rootNode );
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
Reference in New Issue
Block a user