1 // environment_ctrl.cxx -- manager for natural environment information.
3 // Written by David Megginson, started February 2002.
4 // Partly rewritten by Torsten Dreyer, August 2010.
6 // Copyright (C) 2002 David Megginson - david@megginson.com
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/math/SGMath.hxx>
30 #include <Main/fg_props.hxx>
31 #include "environment_ctrl.hxx"
32 #include "environment.hxx"
34 namespace Environment {
37 * @brief Describes an element of a LayerTable. A defined environment at a given altitude.
39 struct LayerTableBucket {
41 FGEnvironment environment;
42 inline bool operator< (const LayerTableBucket &b) const {
43 return (altitude_ft < b.altitude_ft);
46 * @brief LessThan predicate for bucket pointers.
48 static bool lessThan(LayerTableBucket *a, LayerTableBucket *b) {
49 return (a->altitude_ft) < (b->altitude_ft);
53 //////////////////////////////////////////////////////////////////////////////
56 * @brief Models a column of our atmosphere by stacking a number of environments above
59 class LayerTable : public std::vector<LayerTableBucket *>, public SGPropertyChangeListener
62 LayerTable( SGPropertyNode_ptr rootNode ) :
63 _rootNode(rootNode) {}
68 * @brief Read the environment column from properties relative to the given root node
69 * @param environment A template environment to copy values from, not given in the configuration
71 void read( FGEnvironment * parent = NULL );
74 *@brief Interpolate and write environment values for a given altitude
75 *@param altitude_ft The altitude for the desired environment
76 *@environment the destination to write the resulting environment properties to
78 void interpolate(double altitude_ft, FGEnvironment * environment);
81 *@brief Bind all environments properties to property nodes and initialize the listeners
86 *@brief Unbind all environments properties from property nodes and deregister listeners
91 * @brief Implementation of SGProertyChangeListener::valueChanged()
92 * Takes care of consitent sea level pressure for the entire column
94 void valueChanged( SGPropertyNode * node );
95 SGPropertyNode_ptr _rootNode;
98 //////////////////////////////////////////////////////////////////////////////
102 *@brief Implementation of the LayerIterpolateController
104 class LayerInterpolateControllerImplementation : public LayerInterpolateController
107 LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode );
109 virtual void init ();
110 virtual void reinit ();
111 virtual void postinit();
113 virtual void unbind();
114 virtual void update (double delta_time_sec);
117 SGPropertyNode_ptr _rootNode;
119 double _boundary_transition;
120 SGPropertyNode_ptr _altitude_n;
121 SGPropertyNode_ptr _altitude_agl_n;
123 LayerTable _boundary_table;
124 LayerTable _aloft_table;
126 FGEnvironment _environment;
127 simgear::TiedPropertyList _tiedProperties;
130 //////////////////////////////////////////////////////////////////////////////
132 LayerTable::~LayerTable()
134 for( iterator it = begin(); it != end(); it++ )
138 void LayerTable::read(FGEnvironment * parent )
140 double last_altitude_ft = 0.0;
141 double sort_required = false;
144 for (i = 0; i < (size_t)_rootNode->nChildren(); i++) {
145 const SGPropertyNode * child = _rootNode->getChild(i);
146 if ( child->getNameString() == "entry"
147 && child->getStringValue("elevation-ft", "")[0] != '\0'
148 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
150 LayerTableBucket * b;
152 // recycle existing bucket
155 // more nodes than buckets in table, add a new one
156 b = new LayerTableBucket;
159 if (i == 0 && parent != NULL )
160 b->environment = *parent;
162 b->environment = at(i-1)->environment;
164 b->environment.read(child);
165 b->altitude_ft = b->environment.get_elevation_ft();
167 // check, if altitudes are in ascending order
168 if( b->altitude_ft < last_altitude_ft )
169 sort_required = true;
170 last_altitude_ft = b->altitude_ft;
173 // remove leftover buckets
174 while( size() > i ) {
175 LayerTableBucket * b = *(end() - 1);
181 sort(begin(), end(), LayerTableBucket::lessThan);
183 // cleanup entries with (almost)same altitude
184 for( size_type n = 1; n < size(); n++ ) {
185 if( fabs(at(n)->altitude_ft - at(n-1)->altitude_ft ) < 1 ) {
186 SG_LOG( SG_ENVIRONMENT, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << at(n)->altitude_ft );
187 erase( begin() + n );
192 void LayerTable::Bind()
194 // tie all environments to ~/entry[n]/xxx
195 // register this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
196 for( unsigned i = 0; i < size(); i++ ) {
197 SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
198 at(i)->environment.Tie( baseNode );
199 baseNode->getNode( "pressure-sea-level-inhg", true )->addChangeListener( this );
203 void LayerTable::Unbind()
205 // untie all environments to ~/entry[n]/xxx
206 // deregister this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
207 for( unsigned i = 0; i < size(); i++ ) {
208 SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
209 at(i)->environment.Untie();
210 baseNode->getNode( "pressure-sea-level-inhg", true )->removeChangeListener( this );
214 void LayerTable::valueChanged( SGPropertyNode * node )
216 // Make sure all environments in our column use the same sea level pressure
217 double value = node->getDoubleValue();
218 for( iterator it = begin(); it != end(); it++ )
219 (*it)->environment.set_pressure_sea_level_inhg( value );
223 void LayerTable::interpolate( double altitude_ft, FGEnvironment * result )
229 // Boundary conditions
230 if ((length == 1) || (at(0)->altitude_ft >= altitude_ft)) {
231 *result = at(0)->environment; // below bottom of table
233 } else if (at(length-1)->altitude_ft <= altitude_ft) {
234 *result = at(length-1)->environment; // above top of table
238 // Search the interpolation table
240 for ( layer = 1; // can't be below bottom layer, handled above
241 layer < length && at(layer)->altitude_ft <= altitude_ft;
243 FGEnvironment & env1 = (at(layer-1)->environment);
244 FGEnvironment & env2 = (at(layer)->environment);
245 // two layers of same altitude were sorted out in read_table
246 double fraction = ((altitude_ft - at(layer-1)->altitude_ft) /
247 (at(layer)->altitude_ft - at(layer-1)->altitude_ft));
248 env1.interpolate(env2, fraction, result);
251 //////////////////////////////////////////////////////////////////////////////
253 LayerInterpolateControllerImplementation::LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode ) :
254 _rootNode( rootNode ),
256 _boundary_transition(0.0),
257 _altitude_n( fgGetNode("/position/altitude-ft", true)),
258 _altitude_agl_n( fgGetNode("/position/altitude-agl-ft", true)),
259 _boundary_table( rootNode->getNode("boundary", true ) ),
260 _aloft_table( rootNode->getNode("aloft", true ) )
264 void LayerInterpolateControllerImplementation::init ()
266 _boundary_table.read();
267 // pass in a pointer to the environment of the last bondary layer as
269 _aloft_table.read(&(*(_boundary_table.end()-1))->environment);
272 void LayerInterpolateControllerImplementation::reinit ()
274 _boundary_table.Unbind();
275 _aloft_table.Unbind();
280 void LayerInterpolateControllerImplementation::postinit()
282 // we get here after 1. bind() and 2. init() was called by fg_init
283 _boundary_table.Bind();
287 void LayerInterpolateControllerImplementation::bind()
289 // don't bind the layer tables here, because they have not been read in yet.
290 _environment.Tie( _rootNode->getNode( "interpolated", true ) );
291 _tiedProperties.Tie( _rootNode->getNode("enabled", true), &_enabled );
292 _tiedProperties.Tie( _rootNode->getNode("boundary-transition-ft", true ), &_boundary_transition );
295 void LayerInterpolateControllerImplementation::unbind()
297 _boundary_table.Unbind();
298 _aloft_table.Unbind();
299 _tiedProperties.Untie();
300 _environment.Untie();
303 void LayerInterpolateControllerImplementation::update (double delta_time_sec)
305 if( !_enabled || delta_time_sec <= SGLimitsd::min() )
308 double altitude_ft = _altitude_n->getDoubleValue();
309 double altitude_agl_ft = _altitude_agl_n->getDoubleValue();
311 // avoid div by zero later on and init with a default value if not given
312 if( _boundary_transition <= SGLimitsd::min() )
313 _boundary_transition = 500;
315 int length = _boundary_table.size();
318 // If a boundary table is defined, get the top of the boundary layer
319 double boundary_limit = _boundary_table[length-1]->altitude_ft;
320 if (boundary_limit >= altitude_agl_ft) {
321 // If current altitude is below top of boundary layer, interpolate
322 // only in boundary layer
323 _boundary_table.interpolate(altitude_agl_ft, &_environment);
325 } else if ((boundary_limit + _boundary_transition) >= altitude_agl_ft) {
326 // If current altitude is above top of boundary layer and within the
327 // transition altitude, interpolate boundary and aloft layers
328 FGEnvironment env1, env2;
329 _boundary_table.interpolate( altitude_agl_ft, &env1);
330 _aloft_table.interpolate(altitude_ft, &env2);
331 double fraction = (altitude_agl_ft - boundary_limit) / _boundary_transition;
332 env1.interpolate(env2, fraction, &_environment);
336 // If no boundary layer is defined or altitude is above top boundary-layer plus boundary-transition
337 // altitude, use only the aloft table
338 _aloft_table.interpolate( altitude_ft, &_environment);
341 //////////////////////////////////////////////////////////////////////////////
343 LayerInterpolateController * LayerInterpolateController::createInstance( SGPropertyNode_ptr rootNode )
345 return new LayerInterpolateControllerImplementation( rootNode );
348 //////////////////////////////////////////////////////////////////////////////