first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View 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}")

View 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);
}

View 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

File diff suppressed because it is too large Load Diff

197
src/Environment/climate.hxx Normal file
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;

View 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

View 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);
}

View 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
View 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;
}

View 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;
};

View 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

View 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

View 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;

View 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

View 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

View 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

View 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

View 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

View 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}});

View 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
View 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

View 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

View 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

View 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

View 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;

View 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;
};

View 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

View 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