]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/radiostack.cxx
Extend range of ILS in reverse direction to allow back course approaches.
[flightgear.git] / src / Cockpit / radiostack.cxx
1 // radiostack.cxx -- class to manage an instance of the radio stack
2 //
3 // Written by Curtis Olson, started April 2000.
4 //
5 // Copyright (C) 2000  Curtis L. Olson - curt@flightgear.org
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <simgear/compiler.h>
29 #include <simgear/math/sg_random.h>
30
31 #include <Aircraft/aircraft.hxx>
32 #include <Navaids/ilslist.hxx>
33 #include <Navaids/mkrbeacons.hxx>
34 #include <Navaids/navlist.hxx>
35 #include <Time/event.hxx>
36
37 #include "radiostack.hxx"
38
39 static int nav1_play_count = 0;
40 static int nav2_play_count = 0;
41 static int adf_play_count = 0;
42 static time_t nav1_last_time = 0;
43 static time_t nav2_last_time = 0;
44 static time_t adf_last_time = 0;
45
46
47 FGRadioStack *current_radiostack;
48
49
50 /**
51  * Boy, this is ugly!  Make the VOR range vary by altitude difference.
52  */
53 static double kludgeRange ( double stationElev, double aircraftElev,
54                             double nominalRange)
55 {
56                                 // Assume that the nominal range (usually
57                                 // 50nm) applies at a 5,000 ft difference.
58                                 // Just a wild guess!
59   double factor = ((aircraftElev*SG_METER_TO_FEET) - stationElev) / 5000.0;
60   double range = fabs(nominalRange * factor);
61
62                                 // Clamp the range to keep it sane; for
63                                 // now, never less than 25% or more than
64                                 // 500% of nominal range.
65   if (range < nominalRange/4.0) {
66     range = nominalRange/4.0;
67   } else if (range > nominalRange*5.0) {
68     range = nominalRange*5.0;
69   }
70
71   return range;
72 }
73
74
75 // periodic radio station search wrapper
76 static void fgRadioSearch( void ) {
77     current_radiostack->search();
78 }
79
80 // Constructor
81 FGRadioStack::FGRadioStack() :
82     lon_node(fgGetNode("/position/longitude-deg", true)),
83     lat_node(fgGetNode("/position/latitude-deg", true)),
84     alt_node(fgGetNode("/position/altitude-ft", true)),
85     need_update(true),
86     nav1_radial(0.0),
87     nav2_radial(0.0),
88     dme_freq(0.0),
89     dme_dist(0.0),
90     dme_prev_dist(0.0),
91     dme_spd(0.0),
92     dme_ete(0.0)
93 {
94     SGPath path( globals->get_fg_root() );
95     SGPath term = path;
96     term.append( "Navaids/range.term" );
97     SGPath low = path;
98     low.append( "Navaids/range.low" );
99     SGPath high = path;
100     high.append( "Navaids/range.high" );
101
102     term_tbl = new SGInterpTable( term.str() );
103     low_tbl = new SGInterpTable( low.str() );
104     high_tbl = new SGInterpTable( high.str() );
105     dme_last_time.stamp();
106 }
107
108
109 // Destructor
110 FGRadioStack::~FGRadioStack() 
111 {
112     unbind();                   // FIXME: should be called externally
113
114     delete term_tbl;
115     delete low_tbl;
116     delete high_tbl;
117 }
118
119
120 void
121 FGRadioStack::init ()
122 {
123     morse.init();
124     beacon.init();
125     blink.stamp();
126
127     search();
128     update();
129
130     // Search radio database once per second
131     global_events.Register( "fgRadioSearch()", fgRadioSearch,
132                             fgEVENT::FG_EVENT_READY, 1000);
133 }
134
135 void
136 FGRadioStack::bind ()
137 {
138                                 // User inputs
139     fgTie("/radios/nav[0]/frequencies/selected-mhz", this,
140           &FGRadioStack::get_nav1_freq, &FGRadioStack::set_nav1_freq);
141     fgSetArchivable("/radios/nav[0]/frequencies/selected-mhz");
142     fgTie("/radios/nav[0]/frequencies/standby-mhz", this,
143           &FGRadioStack::get_nav1_alt_freq, &FGRadioStack::set_nav1_alt_freq);
144     fgSetArchivable("/radios/nav[0]/frequencies/standby-mhz");
145     fgTie("/radios/nav[0]/radials/selected-deg", this,
146           &FGRadioStack::get_nav1_sel_radial,
147           &FGRadioStack::set_nav1_sel_radial);
148     fgSetArchivable("/radios/nav[0]/radials/selected-deg");
149     fgTie("/radios/nav[0]/volume", this,
150           &FGRadioStack::get_nav1_vol_btn,
151           &FGRadioStack::set_nav1_vol_btn);
152     fgSetArchivable("/radios/nav[0]/volume");
153     fgTie("/radios/nav[0]/ident", this,
154           &FGRadioStack::get_nav1_ident_btn,
155           &FGRadioStack::set_nav1_ident_btn);
156     fgSetArchivable("/radios/nav[0]/ident");
157
158                                 // Radio outputs
159     fgTie("/radios/nav[0]/radials/actual-deg", this,
160           &FGRadioStack::get_nav1_radial);
161     fgTie("/radios/nav[0]/to-flag", this, &FGRadioStack::get_nav1_to_flag);
162     fgTie("/radios/nav[0]/from-flag", this, &FGRadioStack::get_nav1_from_flag);
163     fgTie("/radios/nav[0]/in-range", this, &FGRadioStack::get_nav1_inrange);
164     fgTie("/radios/nav[0]/heading-needle-deflection", this,
165           &FGRadioStack::get_nav1_heading_needle_deflection);
166     fgTie("/radios/nav[0]/gs-needle-deflection", this,
167           &FGRadioStack::get_nav1_gs_needle_deflection);
168
169                                 // User inputs
170     fgTie("/radios/nav[1]/frequencies/selected-mhz", this,
171           &FGRadioStack::get_nav2_freq, &FGRadioStack::set_nav2_freq);
172     fgSetArchivable("/radios/nav[1]/frequencies/selected-mhz");
173     fgTie("/radios/nav[1]/frequencies/standby-mhz", this,
174           &FGRadioStack::get_nav2_alt_freq, &FGRadioStack::set_nav2_alt_freq);
175     fgSetArchivable("/radios/nav[1]/frequencies/standby-mhz");
176     fgTie("/radios/nav[1]/radials/selected-deg", this,
177           &FGRadioStack::get_nav2_sel_radial,
178           &FGRadioStack::set_nav2_sel_radial);
179     fgSetArchivable("/radios/nav[1]/radials/selected-deg");
180     fgTie("/radios/nav[1]/volume", this,
181           &FGRadioStack::get_nav2_vol_btn,
182           &FGRadioStack::set_nav2_vol_btn);
183     fgSetArchivable("/radios/nav[1]/volume");
184     fgTie("/radios/nav[1]/ident", this,
185           &FGRadioStack::get_nav2_ident_btn,
186           &FGRadioStack::set_nav2_ident_btn);
187     fgSetArchivable("/radios/nav[1]/ident");
188
189                                 // Radio outputs
190     fgTie("/radios/nav[1]/radials/actual-deg", this,
191           &FGRadioStack::get_nav2_radial);
192     fgTie("/radios/nav[1]/to-flag", this, &FGRadioStack::get_nav2_to_flag);
193     fgTie("/radios/nav[1]/from-flag", this, &FGRadioStack::get_nav2_from_flag);
194     fgTie("/radios/nav[1]/in-range", this, &FGRadioStack::get_nav2_inrange);
195     fgTie("/radios/nav[1]/heading-needle-deflection", this,
196           &FGRadioStack::get_nav2_heading_needle_deflection);
197     fgTie("/radios/nav[1]/gs-needle-deflection", this,
198           &FGRadioStack::get_nav2_gs_needle_deflection);
199
200                                 // User inputs
201     fgTie("/radios/dme/frequencies/selected-khz", this,
202           &FGRadioStack::get_dme_freq, &FGRadioStack::set_adf_freq);
203
204                                 // Radio outputs
205     fgTie("/radios/dme/in-range", this, &FGRadioStack::get_dme_inrange);
206     fgTie("/radios/dme/distance-nm", this, &FGRadioStack::get_dme_dist);
207     fgTie("/radios/dme/speed-kt", this, &FGRadioStack::get_dme_spd);
208     fgTie("/radios/dme/ete-min", this, &FGRadioStack::get_dme_ete);
209
210                                 // User inputs
211     fgTie("/radios/adf/frequencies/selected-khz", this,
212           &FGRadioStack::get_adf_freq, &FGRadioStack::set_adf_freq);
213     fgSetArchivable("/radios/adf/frequencies/selected-khz");
214     fgTie("/radios/adf/frequencies/standby-khz", this,
215           &FGRadioStack::get_adf_alt_freq, &FGRadioStack::set_adf_alt_freq);
216     fgSetArchivable("/radios/adf/frequencies/standby-khz");
217     fgTie("/radios/adf/rotation-deg", this,
218           &FGRadioStack::get_adf_rotation, &FGRadioStack::set_adf_rotation);
219     fgSetArchivable("/radios/adf/rotation-deg");
220     fgTie("/radios/adf/volume", this,
221           &FGRadioStack::get_adf_vol_btn,
222           &FGRadioStack::set_adf_vol_btn);
223     fgSetArchivable("/radios/adf/volume");
224     fgTie("/radios/adf/ident", this,
225           &FGRadioStack::get_adf_ident_btn,
226           &FGRadioStack::set_adf_ident_btn);
227     fgSetArchivable("/radios/adf/ident");
228
229     fgTie("/radios/marker-beacon/inner", this,
230           &FGRadioStack::get_inner_blink);
231     fgTie("/radios/marker-beacon/middle", this,
232           &FGRadioStack::get_middle_blink);
233     fgTie("/radios/marker-beacon/outer", this,
234           &FGRadioStack::get_outer_blink);
235 }
236
237 void
238 FGRadioStack::unbind ()
239 {
240     fgUntie("/radios/nav[0]/frequencies/selected-mhz");
241     fgUntie("/radios/nav[0]/frequencies/standby-mhz");
242     fgUntie("/radios/nav[0]/radials/actual-deg");
243     fgUntie("/radios/nav[0]/radials/selected-deg");
244     fgUntie("/radios/nav[0]/on");
245     fgUntie("/radios/nav[0]/ident");
246     fgUntie("/radios/nav[0]/to-flag");
247     fgUntie("/radios/nav[0]/from-flag");
248     fgUntie("/radios/nav[0]/in-range");
249     fgUntie("/radios/nav[0]/heading-needle-deflection");
250     fgUntie("/radios/nav[0]/gs-needle-deflection");
251
252     fgUntie("/radios/nav[1]/frequencies/selected-mhz");
253     fgUntie("/radios/nav[1]/frequencies/standby-mhz");
254     fgUntie("/radios/nav[1]/radials/actual-deg");
255     fgUntie("/radios/nav[1]/radials/selected-deg");
256     fgUntie("/radios/nav[1]/on");
257     fgUntie("/radios/nav[1]/ident");
258     fgUntie("/radios/nav[1]/to-flag");
259     fgUntie("/radios/nav[1]/from-flag");
260     fgUntie("/radios/nav[1]/in-range");
261     fgUntie("/radios/nav[1]/heading-needle-deflection");
262     fgUntie("/radios/nav[1]/gs-needle-deflection");
263
264     fgUntie("/radios/dme/frequencies/selected-khz");
265
266                                 // Radio outputs
267     fgUntie("/radios/dme/in-range");
268     fgUntie("/radios/dme/distance-nm");
269     fgUntie("/radios/dme/speed-kt");
270     fgUntie("/radios/dme/ete-min");
271
272     fgUntie("/radios/adf/frequencies/selected-khz");
273     fgUntie("/radios/adf/frequencies/standby-khz");
274     fgUntie("/radios/adf/rotation-deg");
275     fgUntie("/radios/adf/on");
276     fgUntie("/radios/adf/ident");
277
278     fgUntie("/radios/marker-beacon/inner");
279     fgUntie("/radios/marker-beacon/middle");
280     fgUntie("/radios/marker-beacon/outer");
281 }
282
283
284 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
285 double FGRadioStack::adjustNavRange( double stationElev, double aircraftElev,
286                               double nominalRange )
287 {
288     // extend out actual usable range to be 1.3x the published safe range
289     const double usability_factor = 1.3;
290
291     // assumptions we model the standard service volume, plus
292     // ... rather than specifying a cylinder, we model a cone that
293     // contains the cylinder.  Then we put an upside down cone on top
294     // to model diminishing returns at too-high altitudes.
295
296     // altitude difference
297     double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
298     // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
299     //      << " station elev = " << stationElev << endl;
300
301     if ( nominalRange < 25.0 + SG_EPSILON ) {
302         // Standard Terminal Service Volume
303         return term_tbl->interpolate( alt ) * usability_factor;
304     } else if ( nominalRange < 50.0 + SG_EPSILON ) {
305         // Standard Low Altitude Service Volume
306         // table is based on range of 40, scale to actual range
307         return low_tbl->interpolate( alt ) * nominalRange / 40.0
308             * usability_factor;
309     } else {
310         // Standard High Altitude Service Volume
311         // table is based on range of 130, scale to actual range
312         return high_tbl->interpolate( alt ) * nominalRange / 130.0
313             * usability_factor;
314     }
315 }
316
317
318 // model standard ILS service volumes as per AIM 1-1-9
319 double FGRadioStack::adjustILSRange( double stationElev, double aircraftElev,
320                                      double offsetDegrees, double distance )
321 {
322     // assumptions we model the standard service volume, plus
323
324     // altitude difference
325     // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
326     double offset = fabs( offsetDegrees );
327
328     if ( offset < 10 ) {
329         return FG_ILS_DEFAULT_RANGE;
330     } else if ( offset < 35 ) {
331         return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
332     } else if ( offset < 45 ) {
333         return (45 - offset);
334     } else if ( offset > 170 ) {
335         return FG_ILS_DEFAULT_RANGE;
336     } else if ( offset > 145 ) {
337         return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
338     } else if ( offset > 135 ) {
339         return (offset - 135);
340     } else {
341         return 0;
342     }
343 }
344
345
346 // Update the various nav values based on position and valid tuned in navs
347 void 
348 FGRadioStack::update() 
349 {
350     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
351     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
352     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
353
354     need_update = false;
355
356     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
357     Point3D station;
358     double az1, az2, s;
359
360     ////////////////////////////////////////////////////////////////////////
361     // Nav1.
362     ////////////////////////////////////////////////////////////////////////
363
364     if ( nav1_valid ) {
365         station = Point3D( nav1_x, nav1_y, nav1_z );
366         nav1_loc_dist = aircraft.distance3D( station );
367
368         if ( nav1_has_gs ) {
369             station = Point3D( nav1_gs_x, nav1_gs_y, nav1_gs_z );
370             nav1_gs_dist = aircraft.distance3D( station );
371         } else {
372             nav1_gs_dist = 0.0;
373         }
374         
375         // wgs84 heading
376         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
377                             nav1_loclat, nav1_loclon,
378                             &az1, &az2, &s );
379         // cout << "az1 = " << az1 << " magvar = " << nav1_magvar << endl;
380         nav1_heading = az1 - nav1_magvar;
381         // cout << " heading = " << nav1_heading
382         //      << " dist = " << nav1_dist << endl;
383
384         if ( nav1_loc ) {
385             double offset = nav1_heading - nav1_radial;
386             while ( offset < -180.0 ) { offset += 360.0; }
387             while ( offset > 180.0 ) { offset -= 360.0; }
388             // cout << "ils offset = " << offset << endl;
389             nav1_effective_range = adjustILSRange(nav1_elev, elev, offset,
390                                                   nav1_loc_dist * SG_METER_TO_NM );
391         } else {
392             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
393         }
394         // cout << "nav1 range = " << nav1_effective_range
395         //      << " (" << nav1_range << ")" << endl;
396
397         if ( nav1_loc_dist < nav1_effective_range * SG_NM_TO_METER ) {
398             nav1_inrange = true;
399         } else if ( nav1_loc_dist < 2 * nav1_effective_range * SG_NM_TO_METER ) {
400             nav1_inrange = sg_random() < 
401                 ( 2 * nav1_effective_range * SG_NM_TO_METER - nav1_loc_dist ) /
402                 (nav1_effective_range * SG_NM_TO_METER);
403         } else {
404             nav1_inrange = false;
405         }
406
407         if ( !nav1_loc ) {
408             nav1_radial = nav1_sel_radial;
409         }
410     } else {
411         nav1_inrange = false;
412         // cout << "not picking up vor. :-(" << endl;
413     }
414
415 #ifdef ENABLE_AUDIO_SUPPORT
416     if ( nav1_valid && nav1_inrange ) {
417         // play station ident via audio system if on + ident,
418         // otherwise turn it off
419         if ( nav1_vol_btn > 0.1 && nav1_ident_btn ) {
420             FGSimpleSound *sound;
421             sound = globals->get_soundmgr()->find( "nav1-vor-ident" );
422             sound->set_volume( nav1_vol_btn * 0.3 );
423             sound = globals->get_soundmgr()->find( "nav1-dme-ident" );
424             sound->set_volume( nav1_vol_btn * 0.3 );
425             if ( nav1_last_time <
426                  globals->get_time_params()->get_cur_time() - 30 ) {
427                 nav1_last_time = globals->get_time_params()->get_cur_time();
428                 nav1_play_count = 0;
429             }
430             if ( nav1_play_count < 4 ) {
431                 // play VOR ident
432                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") ) {
433                     globals->get_soundmgr()->play_once( "nav1-vor-ident" );
434                     ++nav1_play_count;
435                 }
436             } else if ( nav1_play_count < 5 && nav1_has_dme ) {
437                 // play DME ident
438                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") &&
439                      !globals->get_soundmgr()->is_playing("nav1-dme-ident") ) {
440                     globals->get_soundmgr()->play_once( "nav1-dme-ident" );
441                     ++nav1_play_count;
442                 }
443             }
444         } else {
445             globals->get_soundmgr()->stop( "nav1-vor-ident" );
446             globals->get_soundmgr()->stop( "nav1-dme-ident" );
447         }
448     }
449 #endif
450
451
452     ////////////////////////////////////////////////////////////////////////
453     // Nav2.
454     ////////////////////////////////////////////////////////////////////////
455
456     if ( nav2_valid ) {
457         station = Point3D( nav2_x, nav2_y, nav2_z );
458         nav2_loc_dist = aircraft.distance3D( station );
459
460         if ( nav2_has_gs ) {
461             station = Point3D( nav2_gs_x, nav2_gs_y, nav2_gs_z );
462             nav2_gs_dist = aircraft.distance3D( station );
463         } else {
464             nav2_gs_dist = 0.0;
465         }
466
467         // wgs84 heading
468         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
469                             nav2_loclat, nav2_loclon,
470                             &az1, &az2, &s );
471         nav2_heading = az1 - nav2_magvar;
472         // cout << " heading = " << nav2_heading
473         //      << " dist = " << nav2_dist << endl;
474
475         if ( nav2_loc ) {
476             double offset = nav2_heading - nav2_radial;
477             while ( offset < -180.0 ) { offset += 360.0; }
478             while ( offset > 180.0 ) { offset -= 360.0; }
479             // cout << "ils offset = " << offset << endl;
480             nav2_effective_range = adjustILSRange(nav2_elev, elev, offset,
481                                                   nav2_loc_dist * SG_METER_TO_NM );
482         } else {
483             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
484         }
485         // cout << "nav2 range = " << nav2_effective_range
486         //      << " (" << nav2_range << ")" << endl;
487
488         if ( nav2_loc_dist < nav2_effective_range * SG_NM_TO_METER ) {
489             nav2_inrange = true;
490         } else if ( nav2_loc_dist < 2 * nav2_effective_range * SG_NM_TO_METER ) {
491             nav2_inrange = sg_random() < 
492                 ( 2 * nav2_effective_range * SG_NM_TO_METER - nav2_loc_dist ) /
493                 (nav2_effective_range * SG_NM_TO_METER);
494         } else {
495             nav2_inrange = false;
496         }
497
498         if ( !nav2_loc ) {
499             nav2_radial = nav2_sel_radial;
500         }
501     } else {
502         nav2_inrange = false;
503         // cout << "not picking up vor. :-(" << endl;
504     }
505
506 #ifdef ENABLE_AUDIO_SUPPORT
507     if ( nav2_valid && nav2_inrange ) {
508         // play station ident via audio system if on + ident,
509         // otherwise turn it off
510         if ( nav2_vol_btn > 0.1 && nav2_ident_btn ) {
511             FGSimpleSound *sound;
512             sound = globals->get_soundmgr()->find( "nav2-vor-ident" );
513             sound->set_volume( nav2_vol_btn * 0.3 );
514             sound = globals->get_soundmgr()->find( "nav2-dme-ident" );
515             sound->set_volume( nav2_vol_btn * 0.3 );
516             if ( nav2_last_time <
517                  globals->get_time_params()->get_cur_time() - 30 ) {
518                 nav2_last_time = globals->get_time_params()->get_cur_time();
519                 nav2_play_count = 0;
520             }
521             if ( nav2_play_count < 4 ) {
522                 // play VOR ident
523                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") ) {
524                     globals->get_soundmgr()->play_once( "nav2-vor-ident" );
525                     ++nav2_play_count;
526                 }
527             } else if ( nav2_play_count < 5 && nav2_has_dme ) {
528                 // play DME ident
529                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") &&
530                      !globals->get_soundmgr()->is_playing("nav2-dme-ident") ) {
531                     globals->get_soundmgr()->play_once( "nav2-dme-ident" );
532                     ++nav2_play_count;
533                 }
534             }
535         } else {
536             globals->get_soundmgr()->stop( "nav2-vor-ident" );
537             globals->get_soundmgr()->stop( "nav2-dme-ident" );
538         }
539     }
540 #endif
541
542
543     ////////////////////////////////////////////////////////////////////////
544     // DME.
545     ////////////////////////////////////////////////////////////////////////
546
547     if (dme_valid) {
548         station = Point3D( dme_x, dme_y, dme_z );
549         dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
550         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
551         if (dme_dist < dme_effective_range * SG_NM_TO_METER) {
552           dme_inrange = true;
553         } else if (dme_dist < 2 * dme_effective_range * SG_NM_TO_METER) {
554           dme_inrange = sg_random() <
555             (2 * dme_effective_range * SG_NM_TO_METER - dme_dist) /
556             (dme_effective_range * SG_NM_TO_METER);
557         } else {
558           dme_inrange = false;
559         }
560         if (dme_inrange) {
561           SGTimeStamp current_time;
562           station = Point3D( dme_x, dme_y, dme_z );
563           dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
564           current_time.stamp();
565           long dMs = (current_time - dme_last_time) / 1000;
566                                 // Update every second
567           if (dMs >= 1000) {
568             double dDist = dme_dist - dme_prev_dist;
569             dme_spd = fabs((dDist/dMs) * 3600000);
570                                 // FIXME: the panel should be able to
571                                 // handle this!!!
572             if (dme_spd > 999.0)
573               dme_spd = 999.0;
574             dme_ete = fabs((dme_dist/dme_spd) * 60.0);
575                                 // FIXME: the panel should be able to
576                                 // handle this!!!
577             if (dme_ete > 99.0)
578               dme_ete = 99.0;
579             dme_prev_dist = dme_dist;
580             dme_last_time.stamp();
581           }
582         }
583     } else {
584       dme_inrange = false;
585       dme_dist = 0.0;
586       dme_prev_dist = 0.0;
587     }
588
589
590     ////////////////////////////////////////////////////////////////////////
591     // ADF
592     ////////////////////////////////////////////////////////////////////////
593
594     if ( adf_valid ) {
595         // staightline distance
596         station = Point3D( adf_x, adf_y, adf_z );
597         adf_dist = aircraft.distance3D( station );
598
599         // wgs84 heading
600         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
601                             adf_lat, adf_lon,
602                             &az1, &az2, &s );
603         adf_heading = az1;
604         // cout << " heading = " << nav2_heading
605         //      << " dist = " << nav2_dist << endl;
606
607         adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
608         if ( adf_dist < adf_effective_range * SG_NM_TO_METER ) {
609             adf_inrange = true;
610         } else if ( adf_dist < 2 * adf_effective_range * SG_NM_TO_METER ) {
611             adf_inrange = sg_random() < 
612                 ( 2 * adf_effective_range * SG_NM_TO_METER - adf_dist ) /
613                 (adf_effective_range * SG_NM_TO_METER);
614         } else {
615             adf_inrange = false;
616         }
617     } else {
618         adf_inrange = false;
619     }
620
621 #ifdef ENABLE_AUDIO_SUPPORT
622     if ( adf_valid && adf_inrange ) {
623         // play station ident via audio system if on + ident,
624         // otherwise turn it off
625         if ( adf_vol_btn > 0.1 && adf_ident_btn ) {
626             FGSimpleSound *sound;
627             sound = globals->get_soundmgr()->find( "adf-ident" );
628             sound->set_volume( adf_vol_btn * 0.3 );
629             if ( adf_last_time <
630                  globals->get_time_params()->get_cur_time() - 30 ) {
631                 adf_last_time = globals->get_time_params()->get_cur_time();
632                 adf_play_count = 0;
633             }
634             if ( adf_play_count < 4 ) {
635                 // play ADF ident
636                 if ( !globals->get_soundmgr()->is_playing("adf-ident") ) {
637                     globals->get_soundmgr()->play_once( "adf-ident" );
638                     ++adf_play_count;
639                 }
640             }
641         } else {
642             globals->get_soundmgr()->stop( "adf-ident" );
643         }
644     }
645 #endif
646
647     // marker beacon blinking
648     bool light_on = ( outer_blink || middle_blink || inner_blink );
649     SGTimeStamp current;
650     current.stamp();
651
652     if ( light_on && (current - blink > 400000) ) {
653         light_on = false;
654         blink.stamp();
655     } else if ( !light_on && (current - blink > 100000) ) {
656         light_on = true;
657         blink.stamp();
658     }
659
660     if ( outer_marker ) {
661         outer_blink = light_on;
662     } else {
663         outer_blink = false;
664     }
665
666     if ( middle_marker ) {
667         middle_blink = light_on;
668     } else {
669         middle_blink = false;
670     }
671
672     if ( inner_marker ) {
673         inner_blink = light_on;
674     } else {
675         inner_blink = false;
676     }
677
678     // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
679 }
680
681
682 // Update current nav/adf radio stations based on current postition
683 void FGRadioStack::search() 
684 {
685     static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
686
687     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
688     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
689     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
690
691                                 // FIXME: the panel should handle this
692                                 // don't worry about overhead for now,
693                                 // since this is handled only periodically
694     int dme_switch_pos = fgGetInt("/panel/dme/switch-position");
695     if (dme_switch_pos == 0) {
696       dme_freq = 0;
697       dme_inrange = false;
698     } else if (dme_switch_pos == 1) {
699       if (dme_freq != nav1_freq) {
700         dme_freq = nav1_freq;
701         need_update = true;
702       }
703     } else if (dme_switch_pos == 3) {
704       if (dme_freq != nav2_freq) {
705         dme_freq = nav2_freq;
706         need_update = true;
707       }
708     }
709
710     FGILS ils;
711     FGNav nav;
712
713     static string last_nav1_ident = "";
714     static string last_nav2_ident = "";
715     static string last_adf_ident = "";
716     static bool last_nav1_vor = false;
717     static bool last_nav2_vor = false;
718
719
720     ////////////////////////////////////////////////////////////////////////
721     // Nav1.
722     ////////////////////////////////////////////////////////////////////////
723
724     if ( current_ilslist->query( lon, lat, elev, nav1_freq, &ils ) ) {
725         nav1_ident = ils.get_locident();
726         nav1_valid = true;
727         if ( last_nav1_ident != nav1_ident || last_nav1_vor ) {
728             nav1_trans_ident = ils.get_trans_ident();
729             last_nav1_ident = nav1_ident;
730             last_nav1_vor = false;
731             nav1_loc = true;
732             nav1_has_dme = ils.get_has_dme();
733             nav1_has_gs = ils.get_has_gs();
734
735             nav1_loclon = ils.get_loclon();
736             nav1_loclat = ils.get_loclat();
737             nav1_gslon = ils.get_gslon();
738             nav1_gslat = ils.get_gslat();
739             nav1_elev = ils.get_gselev();
740             nav1_magvar = 0;
741             nav1_range = FG_ILS_DEFAULT_RANGE;
742             nav1_effective_range = nav1_range;
743             nav1_target_gs = ils.get_gsangle();
744             nav1_radial = ils.get_locheading();
745             while ( nav1_radial <   0.0 ) { nav1_radial += 360.0; }
746             while ( nav1_radial > 360.0 ) { nav1_radial -= 360.0; }
747             nav1_x = ils.get_x();
748             nav1_y = ils.get_y();
749             nav1_z = ils.get_z();
750             nav1_gs_x = ils.get_gs_x();
751             nav1_gs_y = ils.get_gs_y();
752             nav1_gs_z = ils.get_gs_z();
753
754 #ifdef ENABLE_AUDIO_SUPPORT
755             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
756                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
757             }
758             FGSimpleSound *sound;
759             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
760             sound->set_volume( 0.3 );
761             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
762
763             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
764                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
765             }
766             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
767             sound->set_volume( 0.3 );
768             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
769
770             int offset = (int)(sg_random() * 30.0);
771             nav1_play_count = offset / 4;
772             nav1_last_time = globals->get_time_params()->get_cur_time() -
773                 offset;
774             // cout << "offset = " << offset << " play_count = "
775             //      << nav1_play_count
776             //      << " nav1_last_time = " << nav1_last_time
777             //      << " current time = "
778             //      << globals->get_time_params()->get_cur_time() << endl;
779 #endif
780
781             // cout << "Found an ils station in range" << endl;
782             // cout << " id = " << ils.get_locident() << endl;
783         }
784     } else if ( current_navlist->query( lon, lat, elev, nav1_freq, &nav ) ) {
785         nav1_ident = nav.get_ident();
786         nav1_valid = true;
787         if ( last_nav1_ident != nav1_ident || !last_nav1_vor ) {
788             last_nav1_ident = nav1_ident;
789             last_nav1_vor = true;
790             nav1_trans_ident = nav.get_trans_ident();
791             nav1_loc = false;
792             nav1_has_dme = nav.get_has_dme();
793             nav1_has_gs = false;
794             nav1_loclon = nav.get_lon();
795             nav1_loclat = nav.get_lat();
796             nav1_elev = nav.get_elev();
797             nav1_magvar = nav.get_magvar();
798             nav1_range = nav.get_range();
799             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
800             nav1_target_gs = 0.0;
801             nav1_radial = nav1_sel_radial;
802             nav1_x = nav.get_x();
803             nav1_y = nav.get_y();
804             nav1_z = nav.get_z();
805
806 #ifdef ENABLE_AUDIO_SUPPORT
807             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
808                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
809             }
810             FGSimpleSound *sound;
811             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
812             sound->set_volume( 0.3 );
813             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
814
815             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
816                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
817             }
818             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
819             sound->set_volume( 0.3 );
820             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
821
822             int offset = (int)(sg_random() * 30.0);
823             nav1_play_count = offset / 4;
824             nav1_last_time = globals->get_time_params()->get_cur_time() -
825                 offset;
826             // cout << "offset = " << offset << " play_count = "
827             //      << nav1_play_count << " nav1_last_time = "
828             //      << nav1_last_time << " current time = "
829             //      << globals->get_time_params()->get_cur_time() << endl;
830 #endif
831
832             // cout << "Found a vor station in range" << endl;
833             // cout << " id = " << nav.get_ident() << endl;
834         }
835     } else {
836         nav1_valid = false;
837         nav1_ident = "";
838         nav1_radial = 0;
839         nav1_trans_ident = "";
840         last_nav1_ident = "";
841 #ifdef ENABLE_AUDIO_SUPPORT
842         globals->get_soundmgr()->remove( "nav1-vor-ident" );
843         globals->get_soundmgr()->remove( "nav1-dme-ident" );
844 #endif
845         // cout << "not picking up vor1. :-(" << endl;
846     }
847
848
849     ////////////////////////////////////////////////////////////////////////
850     // Nav2.
851     ////////////////////////////////////////////////////////////////////////
852
853     if ( current_ilslist->query( lon, lat, elev, nav2_freq, &ils ) ) {
854         nav2_ident = ils.get_locident();
855         nav2_valid = true;
856         if ( last_nav2_ident != nav2_ident || last_nav2_vor ) {
857             last_nav2_ident = nav2_ident;
858             last_nav2_vor = false;
859             nav2_trans_ident = ils.get_trans_ident();
860             nav2_loc = true;
861             nav2_has_dme = ils.get_has_dme();
862             nav2_has_gs = ils.get_has_gs();
863
864             nav2_loclon = ils.get_loclon();
865             nav2_loclat = ils.get_loclat();
866             nav2_elev = ils.get_gselev();
867             nav2_magvar = 0;
868             nav2_range = FG_ILS_DEFAULT_RANGE;
869             nav2_effective_range = nav2_range;
870             nav2_target_gs = ils.get_gsangle();
871             nav2_radial = ils.get_locheading();
872             while ( nav2_radial <   0.0 ) { nav2_radial += 360.0; }
873             while ( nav2_radial > 360.0 ) { nav2_radial -= 360.0; }
874             nav2_x = ils.get_x();
875             nav2_y = ils.get_y();
876             nav2_z = ils.get_z();
877             nav2_gs_x = ils.get_gs_x();
878             nav2_gs_y = ils.get_gs_y();
879             nav2_gs_z = ils.get_gs_z();
880
881 #ifdef ENABLE_AUDIO_SUPPORT
882             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
883                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
884             }
885             FGSimpleSound *sound;
886             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
887             sound->set_volume( 0.3 );
888             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
889
890             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
891                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
892             }
893             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
894             sound->set_volume( 0.3 );
895             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
896
897             int offset = (int)(sg_random() * 30.0);
898             nav2_play_count = offset / 4;
899             nav2_last_time = globals->get_time_params()->get_cur_time() -
900                 offset;
901             // cout << "offset = " << offset << " play_count = "
902             //      << nav2_play_count << " nav2_last_time = "
903             //      << nav2_last_time << " current time = "
904             //      << globals->get_time_params()->get_cur_time() << endl;
905 #endif
906
907             // cout << "Found an ils station in range" << endl;
908             // cout << " id = " << ils.get_locident() << endl;
909         }
910     } else if ( current_navlist->query( lon, lat, elev, nav2_freq, &nav ) ) {
911         nav2_ident = nav.get_ident();
912         nav2_valid = true;
913         if ( last_nav2_ident != nav2_ident || !last_nav2_vor ) {
914             last_nav2_ident = nav2_ident;
915             last_nav2_vor = true;
916             nav2_trans_ident = nav.get_trans_ident();
917             nav2_loc = false;
918             nav2_has_dme = nav.get_has_dme();
919             nav2_has_dme = false;
920             nav2_loclon = nav.get_lon();
921             nav2_loclat = nav.get_lat();
922             nav2_elev = nav.get_elev();
923             nav2_magvar = nav.get_magvar();
924             nav2_range = nav.get_range();
925             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
926             nav2_target_gs = 0.0;
927             nav2_radial = nav2_sel_radial;
928             nav2_x = nav.get_x();
929             nav2_y = nav.get_y();
930             nav2_z = nav.get_z();
931
932 #ifdef ENABLE_AUDIO_SUPPORT
933             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
934                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
935             }
936             FGSimpleSound *sound;
937             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
938             sound->set_volume( 0.3 );
939             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
940
941             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
942                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
943             }
944             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
945             sound->set_volume( 0.3 );
946             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
947
948             int offset = (int)(sg_random() * 30.0);
949             nav2_play_count = offset / 4;
950             nav2_last_time = globals->get_time_params()->get_cur_time() -
951                 offset;
952             // cout << "offset = " << offset << " play_count = "
953             //      << nav2_play_count << " nav2_last_time = "
954             //      << nav2_last_time << " current time = "
955             //      << globals->get_time_params()->get_cur_time() << endl;
956 #endif
957
958             // cout << "Found a vor station in range" << endl;
959             // cout << " id = " << nav.get_ident() << endl;
960         }
961     } else {
962         nav2_valid = false;
963         nav2_ident = "";
964         nav2_radial = 0;
965         nav2_trans_ident = "";
966         last_nav2_ident = "";
967 #ifdef ENABLE_AUDIO_SUPPORT
968         globals->get_soundmgr()->remove( "nav2-vor-ident" );
969         globals->get_soundmgr()->remove( "nav2-dme-ident" );
970 #endif
971         // cout << "not picking up vor1. :-(" << endl;
972     }
973
974
975     ////////////////////////////////////////////////////////////////////////
976     // DME
977     ////////////////////////////////////////////////////////////////////////
978
979     if ( current_ilslist->query( lon, lat, elev, dme_freq, &ils ) ) {
980       if (ils.get_has_dme()) {
981         dme_valid = true;
982         dme_lon = ils.get_loclon();
983         dme_lat = ils.get_loclat();
984         dme_elev = ils.get_gselev();
985         dme_range = FG_ILS_DEFAULT_RANGE;
986         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
987         dme_x = ils.get_dme_x();
988         dme_y = ils.get_dme_y();
989         dme_z = ils.get_dme_z();
990       }
991     } else if ( current_navlist->query( lon, lat, elev, dme_freq, &nav ) ) {
992       if (nav.get_has_dme()) {
993         dme_valid = true;
994         dme_lon = nav.get_lon();
995         dme_lat = nav.get_lat();
996         dme_elev = nav.get_elev();
997         dme_range = nav.get_range();
998         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
999         dme_x = nav.get_x();
1000         dme_y = nav.get_y();
1001         dme_z = nav.get_z();
1002       }
1003     } else {
1004       dme_valid = false;
1005       dme_dist = 0;
1006     }
1007
1008
1009     ////////////////////////////////////////////////////////////////////////
1010     // Beacons.
1011     ////////////////////////////////////////////////////////////////////////
1012
1013     FGMkrBeacon::fgMkrBeacType beacon_type
1014         = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
1015                                   lat * SGD_RADIANS_TO_DEGREES, elev );
1016
1017     outer_marker = middle_marker = inner_marker = false;
1018
1019     if ( beacon_type == FGMkrBeacon::OUTER ) {
1020         outer_marker = true;
1021         // cout << "OUTER MARKER" << endl;
1022 #ifdef ENABLE_AUDIO_SUPPORT
1023         if ( last_beacon != FGMkrBeacon::OUTER ) {
1024             if ( ! globals->get_soundmgr()->exists( "outer-marker" ) ) {
1025                 FGSimpleSound *sound = beacon.get_outer();
1026                 sound->set_volume( 0.3 );
1027                 globals->get_soundmgr()->add( sound, "outer-marker" );
1028             }
1029             if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
1030                 globals->get_soundmgr()->play_looped( "outer-marker" );
1031             }
1032         }
1033 #endif
1034     } else if ( beacon_type == FGMkrBeacon::MIDDLE ) {
1035         middle_marker = true;
1036         // cout << "MIDDLE MARKER" << endl;
1037 #ifdef ENABLE_AUDIO_SUPPORT
1038         if ( last_beacon != FGMkrBeacon::MIDDLE ) {
1039             if ( ! globals->get_soundmgr()->exists( "middle-marker" ) ) {
1040                 FGSimpleSound *sound = beacon.get_middle();
1041                 sound->set_volume( 0.3 );
1042                 globals->get_soundmgr()->add( sound, "middle-marker" );
1043             }
1044             if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
1045                 globals->get_soundmgr()->play_looped( "middle-marker" );
1046             }
1047         }
1048 #endif
1049     } else if ( beacon_type == FGMkrBeacon::INNER ) {
1050         inner_marker = true;
1051         // cout << "INNER MARKER" << endl;
1052 #ifdef ENABLE_AUDIO_SUPPORT
1053         if ( last_beacon != FGMkrBeacon::INNER ) {
1054             if ( ! globals->get_soundmgr()->exists( "inner-marker" ) ) {
1055                 FGSimpleSound *sound = beacon.get_inner();
1056                 sound->set_volume( 0.3 );
1057                 globals->get_soundmgr()->add( sound, "inner-marker" );
1058             }
1059             if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
1060                 globals->get_soundmgr()->play_looped( "inner-marker" );
1061             }
1062         }
1063 #endif
1064     } else {
1065         // cout << "no marker" << endl;
1066 #ifdef ENABLE_AUDIO_SUPPORT
1067         globals->get_soundmgr()->stop( "outer-marker" );
1068         globals->get_soundmgr()->stop( "middle-marker" );
1069         globals->get_soundmgr()->stop( "inner-marker" );
1070 #endif
1071     }
1072     last_beacon = beacon_type;
1073
1074
1075     ////////////////////////////////////////////////////////////////////////
1076     // ADF.
1077     ////////////////////////////////////////////////////////////////////////
1078
1079     if ( current_navlist->query( lon, lat, elev, adf_freq, &nav ) ) {
1080         char freq[128];
1081 #if defined( _MSC_VER ) || defined(__MINGW32__)
1082         _snprintf( freq, 10, "%.0f", adf_freq );
1083 #else
1084         snprintf( freq, 10, "%.0f", adf_freq );
1085 #endif
1086         adf_ident = freq;
1087         adf_ident += nav.get_ident();
1088         // cout << "adf ident = " << adf_ident << endl;
1089         adf_valid = true;
1090         if ( last_adf_ident != adf_ident ) {
1091             last_adf_ident = adf_ident;
1092
1093             adf_trans_ident = nav.get_trans_ident();
1094             adf_lon = nav.get_lon();
1095             adf_lat = nav.get_lat();
1096             adf_elev = nav.get_elev();
1097             adf_range = nav.get_range();
1098             adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
1099             adf_x = nav.get_x();
1100             adf_y = nav.get_y();
1101             adf_z = nav.get_z();
1102
1103 #ifdef ENABLE_AUDIO_SUPPORT
1104             if ( globals->get_soundmgr()->exists( "adf-ident" ) ) {
1105                 globals->get_soundmgr()->remove( "adf-ident" );
1106             }
1107             FGSimpleSound *sound;
1108             sound = morse.make_ident( adf_trans_ident, LO_FREQUENCY );
1109             sound->set_volume( 0.3 );
1110             globals->get_soundmgr()->add( sound, "adf-ident" );
1111
1112             int offset = (int)(sg_random() * 30.0);
1113             adf_play_count = offset / 4;
1114             adf_last_time = globals->get_time_params()->get_cur_time() -
1115                 offset;
1116             // cout << "offset = " << offset << " play_count = "
1117             //      << adf_play_count << " adf_last_time = "
1118             //      << adf_last_time << " current time = "
1119             //      << globals->get_time_params()->get_cur_time() << endl;
1120 #endif
1121
1122             // cout << "Found an adf station in range" << endl;
1123             // cout << " id = " << nav.get_ident() << endl;
1124         }
1125     } else {
1126         adf_valid = false;
1127         adf_ident = "";
1128         adf_trans_ident = "";
1129 #ifdef ENABLE_AUDIO_SUPPORT
1130         globals->get_soundmgr()->remove( "adf-ident" );
1131 #endif
1132         last_adf_ident = "";
1133         // cout << "not picking up adf. :-(" << endl;
1134     }
1135 }
1136
1137
1138 // return the amount of heading needle deflection, returns a value
1139 // clamped to the range of ( -10 , 10 )
1140 double FGRadioStack::get_nav1_heading_needle_deflection() const {
1141     double r;
1142
1143     if ( nav1_inrange ) {
1144         r = nav1_heading - nav1_radial;
1145         // cout << "Radial = " << nav1_radial 
1146         //      << "  Bearing = " << nav1_heading << endl;
1147     
1148         while ( r >  180.0 ) { r -= 360.0;}
1149         while ( r < -180.0 ) { r += 360.0;}
1150         if ( fabs(r) > 90.0 ) {
1151             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1152             if ( nav1_loc ) {
1153                 r = -r;
1154             }
1155         }
1156
1157         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1158         if ( nav1_loc ) { r *= 4.0; }
1159         if ( r < -10.0 ) { r = -10.0; }
1160         if ( r >  10.0 ) { r = 10.0; }
1161     } else {
1162         r = 0.0;
1163     }
1164
1165     return r;
1166 }
1167
1168 // return the amount of heading needle deflection, returns a value
1169 // clamped to the range of ( -10 , 10 )
1170 double FGRadioStack::get_nav2_heading_needle_deflection() const {
1171     double r;
1172
1173     if ( nav2_inrange ) {
1174         r = nav2_heading - nav2_radial;
1175         // cout << "Radial = " << nav2_radial 
1176         //      << "  Bearing = " << nav2_heading << endl;
1177     
1178         while (r> 180.0) r-=360.0;
1179         while (r<-180.0) r+=360.0;
1180         if ( fabs(r) > 90.0 )
1181             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1182         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1183         if ( nav2_loc ) r *= 4.0;
1184         if ( r < -10.0 ) r = -10.0;
1185         if ( r > 10.0 ) r = 10.0;
1186     } else {
1187         r = 0.0;
1188     }
1189
1190     return r;
1191 }
1192
1193 // return the amount of glide slope needle deflection (.i.e. the
1194 // number of degrees we are off the glide slope * 5.0
1195 double FGRadioStack::get_nav1_gs_needle_deflection() const {
1196     if ( nav1_inrange && nav1_has_gs ) {
1197         double x = nav1_gs_dist;
1198         double y = (fgGetDouble("/position/altitude-ft") - nav1_elev)
1199             * SG_FEET_TO_METER;
1200         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1201         return (nav1_target_gs - angle) * 5.0;
1202     } else {
1203         return 0.0;
1204     }
1205 }
1206
1207
1208 // return the amount of glide slope needle deflection (.i.e. the
1209 // number of degrees we are off the glide slope * 5.0
1210 double FGRadioStack::get_nav2_gs_needle_deflection() const {
1211     if ( nav2_inrange && nav2_has_gs ) {
1212         double x = nav2_gs_dist;
1213         double y = (fgGetDouble("/position/altitude-ft") - nav2_elev)
1214             * SG_FEET_TO_METER;
1215         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1216         return (nav2_target_gs - angle) * 5.0;
1217     } else {
1218         return 0.0;
1219     }
1220 }
1221
1222
1223 /**
1224  * Return true if the NAV1 TO flag should be active.
1225  */
1226 bool 
1227 FGRadioStack::get_nav1_to_flag () const
1228 {
1229   if (nav1_inrange) {
1230     double offset = fabs(nav1_heading - nav1_radial);
1231     if (nav1_loc)
1232       return true;
1233     else
1234       return (offset <= 90.0 || offset >= 270.0);
1235   } else {
1236     return false;
1237   }
1238 }
1239
1240
1241 /**
1242  * Return true if the NAV1 FROM flag should be active.
1243  */
1244 bool
1245 FGRadioStack::get_nav1_from_flag () const
1246 {
1247   if (nav1_inrange) {
1248     double offset = fabs(nav1_heading - nav1_radial);
1249     if (nav1_loc)
1250       return false;
1251     else
1252       return (offset > 90.0 && offset < 270.0);
1253   } else {
1254     return false;
1255   }
1256 }
1257
1258
1259 /**
1260  * Return true if the NAV2 TO flag should be active.
1261  */
1262 bool 
1263 FGRadioStack::get_nav2_to_flag () const
1264 {
1265   if (nav2_inrange) {
1266     double offset = fabs(nav2_heading - nav2_radial);
1267     if (nav2_loc)
1268       return true;
1269     else
1270       return (offset <= 90.0 || offset >= 270.0);
1271   } else {
1272     return false;
1273   }
1274 }
1275
1276
1277 /**
1278  * Return true if the NAV2 FROM flag should be active.
1279  */
1280 bool
1281 FGRadioStack::get_nav2_from_flag () const
1282 {
1283   if (nav2_inrange) {
1284     double offset = fabs(nav2_heading - nav2_radial);
1285     if (nav2_loc)
1286       return false;
1287     else
1288       return (offset > 90.0 && offset < 270.0);
1289   } else {
1290     return false;
1291   }
1292 }
1293