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 <Main/fg_props.hxx>
30 #include "environment_ctrl.hxx"
31 #include "environment.hxx"
33 namespace Environment {
36 * @brief Describes an element of a LayerTable. A defined environment at a given altitude.
38 struct LayerTableBucket {
40 FGEnvironment environment;
41 inline bool operator< (const LayerTableBucket &b) const {
42 return (altitude_ft < b.altitude_ft);
45 * @brief LessThan predicate for bucket pointers.
47 static bool lessThan(LayerTableBucket *a, LayerTableBucket *b) {
48 return (a->altitude_ft) < (b->altitude_ft);
52 //////////////////////////////////////////////////////////////////////////////
55 * @brief Models a column of our atmosphere by stacking a number of environments above
58 class LayerTable : public std::vector<LayerTableBucket *>, public SGPropertyChangeListener
61 LayerTable( SGPropertyNode_ptr rootNode ) :
62 _rootNode(rootNode) {}
67 * @brief Read the environment column from properties relative to the given root node
68 * @param environment A template environment to copy values from, not given in the configuration
70 void read( FGEnvironment * parent = NULL );
73 *@brief Interpolate and write environment values for a given altitude
74 *@param altitude_ft The altitude for the desired environment
75 *@environment the destination to write the resulting environment properties to
77 void interpolate(double altitude_ft, FGEnvironment * environment);
80 *@brief Bind all environments properties to property nodes and initialize the listeners
85 *@brief Unbind all environments properties from property nodes and deregister listeners
90 * @brief Implementation of SGProertyChangeListener::valueChanged()
91 * Takes care of consitent sea level pressure for the entire column
93 void valueChanged( SGPropertyNode * node );
94 SGPropertyNode_ptr _rootNode;
97 //////////////////////////////////////////////////////////////////////////////
101 *@brief Implementation of the LayerIterpolateController
103 class LayerInterpolateControllerImplementation : public LayerInterpolateController
106 LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode );
108 virtual void init ();
109 virtual void reinit ();
110 virtual void postinit();
112 virtual void unbind();
113 virtual void update (double delta_time_sec);
116 SGPropertyNode_ptr _rootNode;
118 double _boundary_transition;
119 SGPropertyNode_ptr _altitude_n;
120 SGPropertyNode_ptr _altitude_agl_n;
122 LayerTable _boundary_table;
123 LayerTable _aloft_table;
125 FGEnvironment _environment;
126 simgear::TiedPropertyList _tiedProperties;
129 //////////////////////////////////////////////////////////////////////////////
131 LayerTable::~LayerTable()
133 for( iterator it = begin(); it != end(); it++ )
137 void LayerTable::read(FGEnvironment * parent )
139 double last_altitude_ft = 0.0;
140 double sort_required = false;
143 for (i = 0; i < (size_t)_rootNode->nChildren(); i++) {
144 const SGPropertyNode * child = _rootNode->getChild(i);
145 if ( child->getNameString() == "entry"
146 && child->getStringValue("elevation-ft", "")[0] != '\0'
147 && ( child->getDoubleValue("elevation-ft") > 0.1 || i == 0 ) )
149 LayerTableBucket * b;
151 // recycle existing bucket
154 // more nodes than buckets in table, add a new one
155 b = new LayerTableBucket;
158 if (i == 0 && parent != NULL )
159 b->environment = *parent;
161 b->environment = at(i-1)->environment;
163 b->environment.read(child);
164 b->altitude_ft = b->environment.get_elevation_ft();
166 // check, if altitudes are in ascending order
167 if( b->altitude_ft < last_altitude_ft )
168 sort_required = true;
169 last_altitude_ft = b->altitude_ft;
172 // remove leftover buckets
173 while( size() > i ) {
174 LayerTableBucket * b = *(end() - 1);
180 sort(begin(), end(), LayerTableBucket::lessThan);
182 // cleanup entries with (almost)same altitude
183 for( size_type n = 1; n < size(); n++ ) {
184 if( fabs(at(n)->altitude_ft - at(n-1)->altitude_ft ) < 1 ) {
185 SG_LOG( SG_ENVIRONMENT, SG_ALERT, "Removing duplicate altitude entry in environment config for altitude " << at(n)->altitude_ft );
186 erase( begin() + n );
191 void LayerTable::Bind()
193 // tie all environments to ~/entry[n]/xxx
194 // register this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
195 for( unsigned i = 0; i < size(); i++ ) {
196 SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
197 at(i)->environment.Tie( baseNode );
198 baseNode->getNode( "pressure-sea-level-inhg", true )->addChangeListener( this );
202 void LayerTable::Unbind()
204 // untie all environments to ~/entry[n]/xxx
205 // deregister this as a changelistener of ~/entry[n]/pressure-sea-level-inhg
206 for( unsigned i = 0; i < size(); i++ ) {
207 SGPropertyNode_ptr baseNode = _rootNode->getChild("entry", i, true );
208 at(i)->environment.Untie();
209 baseNode->getNode( "pressure-sea-level-inhg", true )->removeChangeListener( this );
213 void LayerTable::valueChanged( SGPropertyNode * node )
215 // Make sure all environments in our column use the same sea level pressure
216 double value = node->getDoubleValue();
217 for( iterator it = begin(); it != end(); it++ )
218 (*it)->environment.set_pressure_sea_level_inhg( value );
222 void LayerTable::interpolate( double altitude_ft, FGEnvironment * result )
228 // Boundary conditions
229 if ((length == 1) || (at(0)->altitude_ft >= altitude_ft)) {
230 *result = at(0)->environment; // below bottom of table
232 } else if (at(length-1)->altitude_ft <= altitude_ft) {
233 *result = at(length-1)->environment; // above top of table
237 // Search the interpolation table
239 for ( layer = 1; // can't be below bottom layer, handled above
240 layer < length && at(layer)->altitude_ft <= altitude_ft;
242 FGEnvironment & env1 = (at(layer-1)->environment);
243 FGEnvironment & env2 = (at(layer)->environment);
244 // two layers of same altitude were sorted out in read_table
245 double fraction = ((altitude_ft - at(layer-1)->altitude_ft) /
246 (at(layer)->altitude_ft - at(layer-1)->altitude_ft));
247 env1.interpolate(env2, fraction, result);
250 //////////////////////////////////////////////////////////////////////////////
252 LayerInterpolateControllerImplementation::LayerInterpolateControllerImplementation( SGPropertyNode_ptr rootNode ) :
253 _rootNode( rootNode ),
255 _boundary_transition(0.0),
256 _altitude_n( fgGetNode("/position/altitude-ft", true)),
257 _altitude_agl_n( fgGetNode("/position/altitude-agl-ft", true)),
258 _boundary_table( rootNode->getNode("boundary", true ) ),
259 _aloft_table( rootNode->getNode("aloft", true ) )
263 void LayerInterpolateControllerImplementation::init ()
265 _boundary_table.read();
266 // pass in a pointer to the environment of the last bondary layer as
268 _aloft_table.read(&(*(_boundary_table.end()-1))->environment);
271 void LayerInterpolateControllerImplementation::reinit ()
273 _boundary_table.Unbind();
274 _aloft_table.Unbind();
279 void LayerInterpolateControllerImplementation::postinit()
281 // we get here after 1. bind() and 2. init() was called by fg_init
282 _boundary_table.Bind();
286 void LayerInterpolateControllerImplementation::bind()
288 // don't bind the layer tables here, because they have not been read in yet.
289 _environment.Tie( _rootNode->getNode( "interpolated", true ) );
290 _tiedProperties.Tie( _rootNode->getNode("enabled", true), &_enabled );
291 _tiedProperties.Tie( _rootNode->getNode("boundary-transition-ft", true ), &_boundary_transition );
294 void LayerInterpolateControllerImplementation::unbind()
296 _boundary_table.Unbind();
297 _aloft_table.Unbind();
298 _tiedProperties.Untie();
299 _environment.Untie();
302 void LayerInterpolateControllerImplementation::update (double delta_time_sec)
304 if( !_enabled || delta_time_sec <= SGLimitsd::min() )
307 double altitude_ft = _altitude_n->getDoubleValue();
308 double altitude_agl_ft = _altitude_agl_n->getDoubleValue();
310 // avoid div by zero later on and init with a default value if not given
311 if( _boundary_transition <= SGLimitsd::min() )
312 _boundary_transition = 500;
314 int length = _boundary_table.size();
317 // If a boundary table is defined, get the top of the boundary layer
318 double boundary_limit = _boundary_table[length-1]->altitude_ft;
319 if (boundary_limit >= altitude_agl_ft) {
320 // If current altitude is below top of boundary layer, interpolate
321 // only in boundary layer
322 _boundary_table.interpolate(altitude_agl_ft, &_environment);
324 } else if ((boundary_limit + _boundary_transition) >= altitude_agl_ft) {
325 // If current altitude is above top of boundary layer and within the
326 // transition altitude, interpolate boundary and aloft layers
327 FGEnvironment env1, env2;
328 _boundary_table.interpolate( altitude_agl_ft, &env1);
329 _aloft_table.interpolate(altitude_ft, &env2);
330 double fraction = (altitude_agl_ft - boundary_limit) / _boundary_transition;
331 env1.interpolate(env2, fraction, &_environment);
335 // If no boundary layer is defined or altitude is above top boundary-layer plus boundary-transition
336 // altitude, use only the aloft table
337 _aloft_table.interpolate( altitude_ft, &_environment);
340 //////////////////////////////////////////////////////////////////////////////
342 LayerInterpolateController * LayerInterpolateController::createInstance( SGPropertyNode_ptr rootNode )
344 return new LayerInterpolateControllerImplementation( rootNode );
347 //////////////////////////////////////////////////////////////////////////////