]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/radiostack.cxx
initialize marker_xs.
[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 {
335         return 0;
336     }
337 }
338
339
340 // Update the various nav values based on position and valid tuned in navs
341 void 
342 FGRadioStack::update() 
343 {
344     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
345     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
346     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
347
348     need_update = false;
349
350     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
351     Point3D station;
352     double az1, az2, s;
353
354     ////////////////////////////////////////////////////////////////////////
355     // Nav1.
356     ////////////////////////////////////////////////////////////////////////
357
358     if ( nav1_valid ) {
359         station = Point3D( nav1_x, nav1_y, nav1_z );
360         nav1_loc_dist = aircraft.distance3D( station );
361
362         if ( nav1_has_gs ) {
363             station = Point3D( nav1_gs_x, nav1_gs_y, nav1_gs_z );
364             nav1_gs_dist = aircraft.distance3D( station );
365         } else {
366             nav1_gs_dist = 0.0;
367         }
368         
369         // wgs84 heading
370         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
371                             nav1_loclat, nav1_loclon,
372                             &az1, &az2, &s );
373         // cout << "az1 = " << az1 << " magvar = " << nav1_magvar << endl;
374         nav1_heading = az1 - nav1_magvar;
375         // cout << " heading = " << nav1_heading
376         //      << " dist = " << nav1_dist << endl;
377
378         if ( nav1_loc ) {
379             double offset = nav1_heading - nav1_radial;
380             while ( offset < -180.0 ) { offset += 360.0; }
381             while ( offset > 180.0 ) { offset -= 360.0; }
382             // cout << "ils offset = " << offset << endl;
383             nav1_effective_range = adjustILSRange(nav1_elev, elev, offset,
384                                                   nav1_loc_dist * SG_METER_TO_NM );
385         } else {
386             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
387         }
388         // cout << "nav1 range = " << nav1_effective_range
389         //      << " (" << nav1_range << ")" << endl;
390
391         if ( nav1_loc_dist < nav1_effective_range * SG_NM_TO_METER ) {
392             nav1_inrange = true;
393         } else if ( nav1_loc_dist < 2 * nav1_effective_range * SG_NM_TO_METER ) {
394             nav1_inrange = sg_random() < 
395                 ( 2 * nav1_effective_range * SG_NM_TO_METER - nav1_loc_dist ) /
396                 (nav1_effective_range * SG_NM_TO_METER);
397         } else {
398             nav1_inrange = false;
399         }
400
401         if ( !nav1_loc ) {
402             nav1_radial = nav1_sel_radial;
403         }
404     } else {
405         nav1_inrange = false;
406         // cout << "not picking up vor. :-(" << endl;
407     }
408
409 #ifdef ENABLE_AUDIO_SUPPORT
410     if ( nav1_valid && nav1_inrange ) {
411         // play station ident via audio system if on + ident,
412         // otherwise turn it off
413         if ( nav1_vol_btn > 0.1 && nav1_ident_btn ) {
414             FGSimpleSound *sound;
415             sound = globals->get_soundmgr()->find( "nav1-vor-ident" );
416             sound->set_volume( nav1_vol_btn * 0.3 );
417             sound = globals->get_soundmgr()->find( "nav1-dme-ident" );
418             sound->set_volume( nav1_vol_btn * 0.3 );
419             if ( nav1_last_time <
420                  globals->get_time_params()->get_cur_time() - 30 ) {
421                 nav1_last_time = globals->get_time_params()->get_cur_time();
422                 nav1_play_count = 0;
423             }
424             if ( nav1_play_count < 4 ) {
425                 // play VOR ident
426                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") ) {
427                     globals->get_soundmgr()->play_once( "nav1-vor-ident" );
428                     ++nav1_play_count;
429                 }
430             } else if ( nav1_play_count < 5 && nav1_has_dme ) {
431                 // play DME ident
432                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") &&
433                      !globals->get_soundmgr()->is_playing("nav1-dme-ident") ) {
434                     globals->get_soundmgr()->play_once( "nav1-dme-ident" );
435                     ++nav1_play_count;
436                 }
437             }
438         } else {
439             globals->get_soundmgr()->stop( "nav1-vor-ident" );
440             globals->get_soundmgr()->stop( "nav1-dme-ident" );
441         }
442     }
443 #endif
444
445
446     ////////////////////////////////////////////////////////////////////////
447     // Nav2.
448     ////////////////////////////////////////////////////////////////////////
449
450     if ( nav2_valid ) {
451         station = Point3D( nav2_x, nav2_y, nav2_z );
452         nav2_loc_dist = aircraft.distance3D( station );
453
454         if ( nav2_has_gs ) {
455             station = Point3D( nav2_gs_x, nav2_gs_y, nav2_gs_z );
456             nav2_gs_dist = aircraft.distance3D( station );
457         } else {
458             nav2_gs_dist = 0.0;
459         }
460
461         // wgs84 heading
462         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
463                             nav2_loclat, nav2_loclon,
464                             &az1, &az2, &s );
465         nav2_heading = az1 - nav2_magvar;
466         // cout << " heading = " << nav2_heading
467         //      << " dist = " << nav2_dist << endl;
468
469         if ( nav2_loc ) {
470             double offset = nav2_heading - nav2_radial;
471             while ( offset < -180.0 ) { offset += 360.0; }
472             while ( offset > 180.0 ) { offset -= 360.0; }
473             // cout << "ils offset = " << offset << endl;
474             nav2_effective_range = adjustILSRange(nav2_elev, elev, offset,
475                                                   nav2_loc_dist * SG_METER_TO_NM );
476         } else {
477             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
478         }
479         // cout << "nav2 range = " << nav2_effective_range
480         //      << " (" << nav2_range << ")" << endl;
481
482         if ( nav2_loc_dist < nav2_effective_range * SG_NM_TO_METER ) {
483             nav2_inrange = true;
484         } else if ( nav2_loc_dist < 2 * nav2_effective_range * SG_NM_TO_METER ) {
485             nav2_inrange = sg_random() < 
486                 ( 2 * nav2_effective_range * SG_NM_TO_METER - nav2_loc_dist ) /
487                 (nav2_effective_range * SG_NM_TO_METER);
488         } else {
489             nav2_inrange = false;
490         }
491
492         if ( !nav2_loc ) {
493             nav2_radial = nav2_sel_radial;
494         }
495     } else {
496         nav2_inrange = false;
497         // cout << "not picking up vor. :-(" << endl;
498     }
499
500 #ifdef ENABLE_AUDIO_SUPPORT
501     if ( nav2_valid && nav2_inrange ) {
502         // play station ident via audio system if on + ident,
503         // otherwise turn it off
504         if ( nav2_vol_btn > 0.1 && nav2_ident_btn ) {
505             FGSimpleSound *sound;
506             sound = globals->get_soundmgr()->find( "nav2-vor-ident" );
507             sound->set_volume( nav2_vol_btn * 0.3 );
508             sound = globals->get_soundmgr()->find( "nav2-dme-ident" );
509             sound->set_volume( nav2_vol_btn * 0.3 );
510             if ( nav2_last_time <
511                  globals->get_time_params()->get_cur_time() - 30 ) {
512                 nav2_last_time = globals->get_time_params()->get_cur_time();
513                 nav2_play_count = 0;
514             }
515             if ( nav2_play_count < 4 ) {
516                 // play VOR ident
517                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") ) {
518                     globals->get_soundmgr()->play_once( "nav2-vor-ident" );
519                     ++nav2_play_count;
520                 }
521             } else if ( nav2_play_count < 5 && nav2_has_dme ) {
522                 // play DME ident
523                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") &&
524                      !globals->get_soundmgr()->is_playing("nav2-dme-ident") ) {
525                     globals->get_soundmgr()->play_once( "nav2-dme-ident" );
526                     ++nav2_play_count;
527                 }
528             }
529         } else {
530             globals->get_soundmgr()->stop( "nav2-vor-ident" );
531             globals->get_soundmgr()->stop( "nav2-dme-ident" );
532         }
533     }
534 #endif
535
536
537     ////////////////////////////////////////////////////////////////////////
538     // DME.
539     ////////////////////////////////////////////////////////////////////////
540
541     if (dme_valid) {
542         station = Point3D( dme_x, dme_y, dme_z );
543         dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
544         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
545         if (dme_dist < dme_effective_range * SG_NM_TO_METER) {
546           dme_inrange = true;
547         } else if (dme_dist < 2 * dme_effective_range * SG_NM_TO_METER) {
548           dme_inrange = sg_random() <
549             (2 * dme_effective_range * SG_NM_TO_METER - dme_dist) /
550             (dme_effective_range * SG_NM_TO_METER);
551         } else {
552           dme_inrange = false;
553         }
554         if (dme_inrange) {
555           SGTimeStamp current_time;
556           station = Point3D( dme_x, dme_y, dme_z );
557           dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
558           current_time.stamp();
559           long dMs = (current_time - dme_last_time) / 1000;
560                                 // Update every second
561           if (dMs >= 1000) {
562             double dDist = dme_dist - dme_prev_dist;
563             dme_spd = fabs((dDist/dMs) * 3600000);
564                                 // FIXME: the panel should be able to
565                                 // handle this!!!
566             if (dme_spd > 999.0)
567               dme_spd = 999.0;
568             dme_ete = fabs((dme_dist/dme_spd) * 60.0);
569                                 // FIXME: the panel should be able to
570                                 // handle this!!!
571             if (dme_ete > 99.0)
572               dme_ete = 99.0;
573             dme_prev_dist = dme_dist;
574             dme_last_time.stamp();
575           }
576         }
577     } else {
578       dme_inrange = false;
579       dme_dist = 0.0;
580       dme_prev_dist = 0.0;
581     }
582
583
584     ////////////////////////////////////////////////////////////////////////
585     // ADF
586     ////////////////////////////////////////////////////////////////////////
587
588     if ( adf_valid ) {
589         // staightline distance
590         station = Point3D( adf_x, adf_y, adf_z );
591         adf_dist = aircraft.distance3D( station );
592
593         // wgs84 heading
594         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
595                             adf_lat, adf_lon,
596                             &az1, &az2, &s );
597         adf_heading = az1;
598         // cout << " heading = " << nav2_heading
599         //      << " dist = " << nav2_dist << endl;
600
601         adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
602         if ( adf_dist < adf_effective_range * SG_NM_TO_METER ) {
603             adf_inrange = true;
604         } else if ( adf_dist < 2 * adf_effective_range * SG_NM_TO_METER ) {
605             adf_inrange = sg_random() < 
606                 ( 2 * adf_effective_range * SG_NM_TO_METER - adf_dist ) /
607                 (adf_effective_range * SG_NM_TO_METER);
608         } else {
609             adf_inrange = false;
610         }
611     } else {
612         adf_inrange = false;
613     }
614
615 #ifdef ENABLE_AUDIO_SUPPORT
616     if ( adf_valid && adf_inrange ) {
617         // play station ident via audio system if on + ident,
618         // otherwise turn it off
619         if ( adf_vol_btn > 0.1 && adf_ident_btn ) {
620             FGSimpleSound *sound;
621             sound = globals->get_soundmgr()->find( "adf-ident" );
622             sound->set_volume( adf_vol_btn * 0.3 );
623             if ( adf_last_time <
624                  globals->get_time_params()->get_cur_time() - 30 ) {
625                 adf_last_time = globals->get_time_params()->get_cur_time();
626                 adf_play_count = 0;
627             }
628             if ( adf_play_count < 4 ) {
629                 // play ADF ident
630                 if ( !globals->get_soundmgr()->is_playing("adf-ident") ) {
631                     globals->get_soundmgr()->play_once( "adf-ident" );
632                     ++adf_play_count;
633                 }
634             }
635         } else {
636             globals->get_soundmgr()->stop( "adf-ident" );
637         }
638     }
639 #endif
640
641     // marker beacon blinking
642     bool light_on = ( outer_blink || middle_blink || inner_blink );
643     SGTimeStamp current;
644     current.stamp();
645
646     if ( light_on && (current - blink > 400000) ) {
647         light_on = false;
648         blink.stamp();
649     } else if ( !light_on && (current - blink > 100000) ) {
650         light_on = true;
651         blink.stamp();
652     }
653
654     if ( outer_marker ) {
655         outer_blink = light_on;
656     } else {
657         outer_blink = false;
658     }
659
660     if ( middle_marker ) {
661         middle_blink = light_on;
662     } else {
663         middle_blink = false;
664     }
665
666     if ( inner_marker ) {
667         inner_blink = light_on;
668     } else {
669         inner_blink = false;
670     }
671
672     // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
673 }
674
675
676 // Update current nav/adf radio stations based on current postition
677 void FGRadioStack::search() 
678 {
679     static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
680
681     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
682     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
683     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
684
685                                 // FIXME: the panel should handle this
686                                 // don't worry about overhead for now,
687                                 // since this is handled only periodically
688     int dme_switch_pos = fgGetInt("/panel/dme/switch-position");
689     if (dme_switch_pos == 0) {
690       dme_freq = 0;
691       dme_inrange = false;
692     } else if (dme_switch_pos == 1) {
693       if (dme_freq != nav1_freq) {
694         dme_freq = nav1_freq;
695         need_update = true;
696       }
697     } else if (dme_switch_pos == 3) {
698       if (dme_freq != nav2_freq) {
699         dme_freq = nav2_freq;
700         need_update = true;
701       }
702     }
703
704     FGILS ils;
705     FGNav nav;
706
707     static string last_nav1_ident = "";
708     static string last_nav2_ident = "";
709     static string last_adf_ident = "";
710     static bool last_nav1_vor = false;
711     static bool last_nav2_vor = false;
712
713
714     ////////////////////////////////////////////////////////////////////////
715     // Nav1.
716     ////////////////////////////////////////////////////////////////////////
717
718     if ( current_ilslist->query( lon, lat, elev, nav1_freq, &ils ) ) {
719         nav1_ident = ils.get_locident();
720         nav1_valid = true;
721         if ( last_nav1_ident != nav1_ident || last_nav1_vor ) {
722             nav1_trans_ident = ils.get_trans_ident();
723             last_nav1_ident = nav1_ident;
724             last_nav1_vor = false;
725             nav1_loc = true;
726             nav1_has_dme = ils.get_has_dme();
727             nav1_has_gs = ils.get_has_gs();
728
729             nav1_loclon = ils.get_loclon();
730             nav1_loclat = ils.get_loclat();
731             nav1_gslon = ils.get_gslon();
732             nav1_gslat = ils.get_gslat();
733             nav1_elev = ils.get_gselev();
734             nav1_magvar = 0;
735             nav1_range = FG_ILS_DEFAULT_RANGE;
736             nav1_effective_range = nav1_range;
737             nav1_target_gs = ils.get_gsangle();
738             nav1_radial = ils.get_locheading();
739             while ( nav1_radial <   0.0 ) { nav1_radial += 360.0; }
740             while ( nav1_radial > 360.0 ) { nav1_radial -= 360.0; }
741             nav1_x = ils.get_x();
742             nav1_y = ils.get_y();
743             nav1_z = ils.get_z();
744             nav1_gs_x = ils.get_gs_x();
745             nav1_gs_y = ils.get_gs_y();
746             nav1_gs_z = ils.get_gs_z();
747
748 #ifdef ENABLE_AUDIO_SUPPORT
749             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
750                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
751             }
752             FGSimpleSound *sound;
753             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
754             sound->set_volume( 0.3 );
755             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
756
757             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
758                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
759             }
760             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
761             sound->set_volume( 0.3 );
762             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
763
764             int offset = (int)(sg_random() * 30.0);
765             nav1_play_count = offset / 4;
766             nav1_last_time = globals->get_time_params()->get_cur_time() -
767                 offset;
768             // cout << "offset = " << offset << " play_count = "
769             //      << nav1_play_count
770             //      << " nav1_last_time = " << nav1_last_time
771             //      << " current time = "
772             //      << globals->get_time_params()->get_cur_time() << endl;
773 #endif
774
775             // cout << "Found an ils station in range" << endl;
776             // cout << " id = " << ils.get_locident() << endl;
777         }
778     } else if ( current_navlist->query( lon, lat, elev, nav1_freq, &nav ) ) {
779         nav1_ident = nav.get_ident();
780         nav1_valid = true;
781         if ( last_nav1_ident != nav1_ident || !last_nav1_vor ) {
782             last_nav1_ident = nav1_ident;
783             last_nav1_vor = true;
784             nav1_trans_ident = nav.get_trans_ident();
785             nav1_loc = false;
786             nav1_has_dme = nav.get_has_dme();
787             nav1_has_gs = false;
788             nav1_loclon = nav.get_lon();
789             nav1_loclat = nav.get_lat();
790             nav1_elev = nav.get_elev();
791             nav1_magvar = nav.get_magvar();
792             nav1_range = nav.get_range();
793             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
794             nav1_target_gs = 0.0;
795             nav1_radial = nav1_sel_radial;
796             nav1_x = nav.get_x();
797             nav1_y = nav.get_y();
798             nav1_z = nav.get_z();
799
800 #ifdef ENABLE_AUDIO_SUPPORT
801             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
802                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
803             }
804             FGSimpleSound *sound;
805             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
806             sound->set_volume( 0.3 );
807             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
808
809             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
810                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
811             }
812             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
813             sound->set_volume( 0.3 );
814             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
815
816             int offset = (int)(sg_random() * 30.0);
817             nav1_play_count = offset / 4;
818             nav1_last_time = globals->get_time_params()->get_cur_time() -
819                 offset;
820             // cout << "offset = " << offset << " play_count = "
821             //      << nav1_play_count << " nav1_last_time = "
822             //      << nav1_last_time << " current time = "
823             //      << globals->get_time_params()->get_cur_time() << endl;
824 #endif
825
826             // cout << "Found a vor station in range" << endl;
827             // cout << " id = " << nav.get_ident() << endl;
828         }
829     } else {
830         nav1_valid = false;
831         nav1_ident = "";
832         nav1_radial = 0;
833         nav1_trans_ident = "";
834         last_nav1_ident = "";
835 #ifdef ENABLE_AUDIO_SUPPORT
836         globals->get_soundmgr()->remove( "nav1-vor-ident" );
837         globals->get_soundmgr()->remove( "nav1-dme-ident" );
838 #endif
839         // cout << "not picking up vor1. :-(" << endl;
840     }
841
842
843     ////////////////////////////////////////////////////////////////////////
844     // Nav2.
845     ////////////////////////////////////////////////////////////////////////
846
847     if ( current_ilslist->query( lon, lat, elev, nav2_freq, &ils ) ) {
848         nav2_ident = ils.get_locident();
849         nav2_valid = true;
850         if ( last_nav2_ident != nav2_ident || last_nav2_vor ) {
851             last_nav2_ident = nav2_ident;
852             last_nav2_vor = false;
853             nav2_trans_ident = ils.get_trans_ident();
854             nav2_loc = true;
855             nav2_has_dme = ils.get_has_dme();
856             nav2_has_gs = ils.get_has_gs();
857
858             nav2_loclon = ils.get_loclon();
859             nav2_loclat = ils.get_loclat();
860             nav2_elev = ils.get_gselev();
861             nav2_magvar = 0;
862             nav2_range = FG_ILS_DEFAULT_RANGE;
863             nav2_effective_range = nav2_range;
864             nav2_target_gs = ils.get_gsangle();
865             nav2_radial = ils.get_locheading();
866             while ( nav2_radial <   0.0 ) { nav2_radial += 360.0; }
867             while ( nav2_radial > 360.0 ) { nav2_radial -= 360.0; }
868             nav2_x = ils.get_x();
869             nav2_y = ils.get_y();
870             nav2_z = ils.get_z();
871             nav2_gs_x = ils.get_gs_x();
872             nav2_gs_y = ils.get_gs_y();
873             nav2_gs_z = ils.get_gs_z();
874
875 #ifdef ENABLE_AUDIO_SUPPORT
876             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
877                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
878             }
879             FGSimpleSound *sound;
880             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
881             sound->set_volume( 0.3 );
882             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
883
884             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
885                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
886             }
887             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
888             sound->set_volume( 0.3 );
889             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
890
891             int offset = (int)(sg_random() * 30.0);
892             nav2_play_count = offset / 4;
893             nav2_last_time = globals->get_time_params()->get_cur_time() -
894                 offset;
895             // cout << "offset = " << offset << " play_count = "
896             //      << nav2_play_count << " nav2_last_time = "
897             //      << nav2_last_time << " current time = "
898             //      << globals->get_time_params()->get_cur_time() << endl;
899 #endif
900
901             // cout << "Found an ils station in range" << endl;
902             // cout << " id = " << ils.get_locident() << endl;
903         }
904     } else if ( current_navlist->query( lon, lat, elev, nav2_freq, &nav ) ) {
905         nav2_ident = nav.get_ident();
906         nav2_valid = true;
907         if ( last_nav2_ident != nav2_ident || !last_nav2_vor ) {
908             last_nav2_ident = nav2_ident;
909             last_nav2_vor = true;
910             nav2_trans_ident = nav.get_trans_ident();
911             nav2_loc = false;
912             nav2_has_dme = nav.get_has_dme();
913             nav2_has_dme = false;
914             nav2_loclon = nav.get_lon();
915             nav2_loclat = nav.get_lat();
916             nav2_elev = nav.get_elev();
917             nav2_magvar = nav.get_magvar();
918             nav2_range = nav.get_range();
919             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
920             nav2_target_gs = 0.0;
921             nav2_radial = nav2_sel_radial;
922             nav2_x = nav.get_x();
923             nav2_y = nav.get_y();
924             nav2_z = nav.get_z();
925
926 #ifdef ENABLE_AUDIO_SUPPORT
927             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
928                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
929             }
930             FGSimpleSound *sound;
931             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
932             sound->set_volume( 0.3 );
933             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
934
935             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
936                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
937             }
938             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
939             sound->set_volume( 0.3 );
940             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
941
942             int offset = (int)(sg_random() * 30.0);
943             nav2_play_count = offset / 4;
944             nav2_last_time = globals->get_time_params()->get_cur_time() -
945                 offset;
946             // cout << "offset = " << offset << " play_count = "
947             //      << nav2_play_count << " nav2_last_time = "
948             //      << nav2_last_time << " current time = "
949             //      << globals->get_time_params()->get_cur_time() << endl;
950 #endif
951
952             // cout << "Found a vor station in range" << endl;
953             // cout << " id = " << nav.get_ident() << endl;
954         }
955     } else {
956         nav2_valid = false;
957         nav2_ident = "";
958         nav2_radial = 0;
959         nav2_trans_ident = "";
960         last_nav2_ident = "";
961 #ifdef ENABLE_AUDIO_SUPPORT
962         globals->get_soundmgr()->remove( "nav2-vor-ident" );
963         globals->get_soundmgr()->remove( "nav2-dme-ident" );
964 #endif
965         // cout << "not picking up vor1. :-(" << endl;
966     }
967
968
969     ////////////////////////////////////////////////////////////////////////
970     // DME
971     ////////////////////////////////////////////////////////////////////////
972
973     if ( current_ilslist->query( lon, lat, elev, dme_freq, &ils ) ) {
974       if (ils.get_has_dme()) {
975         dme_valid = true;
976         dme_lon = ils.get_loclon();
977         dme_lat = ils.get_loclat();
978         dme_elev = ils.get_gselev();
979         dme_range = FG_ILS_DEFAULT_RANGE;
980         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
981         dme_x = ils.get_dme_x();
982         dme_y = ils.get_dme_y();
983         dme_z = ils.get_dme_z();
984       }
985     } else if ( current_navlist->query( lon, lat, elev, dme_freq, &nav ) ) {
986       if (nav.get_has_dme()) {
987         dme_valid = true;
988         dme_lon = nav.get_lon();
989         dme_lat = nav.get_lat();
990         dme_elev = nav.get_elev();
991         dme_range = nav.get_range();
992         dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
993         dme_x = nav.get_x();
994         dme_y = nav.get_y();
995         dme_z = nav.get_z();
996       }
997     } else {
998       dme_valid = false;
999       dme_dist = 0;
1000     }
1001
1002
1003     ////////////////////////////////////////////////////////////////////////
1004     // Beacons.
1005     ////////////////////////////////////////////////////////////////////////
1006
1007     FGMkrBeacon::fgMkrBeacType beacon_type
1008         = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
1009                                   lat * SGD_RADIANS_TO_DEGREES, elev );
1010
1011     outer_marker = middle_marker = inner_marker = false;
1012
1013     if ( beacon_type == FGMkrBeacon::OUTER ) {
1014         outer_marker = true;
1015         // cout << "OUTER MARKER" << endl;
1016 #ifdef ENABLE_AUDIO_SUPPORT
1017         if ( last_beacon != FGMkrBeacon::OUTER ) {
1018             if ( ! globals->get_soundmgr()->exists( "outer-marker" ) ) {
1019                 FGSimpleSound *sound = beacon.get_outer();
1020                 sound->set_volume( 0.3 );
1021                 globals->get_soundmgr()->add( sound, "outer-marker" );
1022             }
1023             if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
1024                 globals->get_soundmgr()->play_looped( "outer-marker" );
1025             }
1026         }
1027 #endif
1028     } else if ( beacon_type == FGMkrBeacon::MIDDLE ) {
1029         middle_marker = true;
1030         // cout << "MIDDLE MARKER" << endl;
1031 #ifdef ENABLE_AUDIO_SUPPORT
1032         if ( last_beacon != FGMkrBeacon::MIDDLE ) {
1033             if ( ! globals->get_soundmgr()->exists( "middle-marker" ) ) {
1034                 FGSimpleSound *sound = beacon.get_middle();
1035                 sound->set_volume( 0.3 );
1036                 globals->get_soundmgr()->add( sound, "middle-marker" );
1037             }
1038             if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
1039                 globals->get_soundmgr()->play_looped( "middle-marker" );
1040             }
1041         }
1042 #endif
1043     } else if ( beacon_type == FGMkrBeacon::INNER ) {
1044         inner_marker = true;
1045         // cout << "INNER MARKER" << endl;
1046 #ifdef ENABLE_AUDIO_SUPPORT
1047         if ( last_beacon != FGMkrBeacon::INNER ) {
1048             if ( ! globals->get_soundmgr()->exists( "inner-marker" ) ) {
1049                 FGSimpleSound *sound = beacon.get_inner();
1050                 sound->set_volume( 0.3 );
1051                 globals->get_soundmgr()->add( sound, "inner-marker" );
1052             }
1053             if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
1054                 globals->get_soundmgr()->play_looped( "inner-marker" );
1055             }
1056         }
1057 #endif
1058     } else {
1059         // cout << "no marker" << endl;
1060 #ifdef ENABLE_AUDIO_SUPPORT
1061         globals->get_soundmgr()->stop( "outer-marker" );
1062         globals->get_soundmgr()->stop( "middle-marker" );
1063         globals->get_soundmgr()->stop( "inner-marker" );
1064 #endif
1065     }
1066     last_beacon = beacon_type;
1067
1068
1069     ////////////////////////////////////////////////////////////////////////
1070     // ADF.
1071     ////////////////////////////////////////////////////////////////////////
1072
1073     if ( current_navlist->query( lon, lat, elev, adf_freq, &nav ) ) {
1074         char freq[128];
1075 #if defined( _MSC_VER )
1076         _snprintf( freq, 10, "%.0f", adf_freq );
1077 #else
1078         snprintf( freq, 10, "%.0f", adf_freq );
1079 #endif
1080         adf_ident = freq;
1081         adf_ident += nav.get_ident();
1082         // cout << "adf ident = " << adf_ident << endl;
1083         adf_valid = true;
1084         if ( last_adf_ident != adf_ident ) {
1085             last_adf_ident = adf_ident;
1086
1087             adf_trans_ident = nav.get_trans_ident();
1088             adf_lon = nav.get_lon();
1089             adf_lat = nav.get_lat();
1090             adf_elev = nav.get_elev();
1091             adf_range = nav.get_range();
1092             adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
1093             adf_x = nav.get_x();
1094             adf_y = nav.get_y();
1095             adf_z = nav.get_z();
1096
1097 #ifdef ENABLE_AUDIO_SUPPORT
1098             if ( globals->get_soundmgr()->exists( "adf-ident" ) ) {
1099                 globals->get_soundmgr()->remove( "adf-ident" );
1100             }
1101             FGSimpleSound *sound;
1102             sound = morse.make_ident( adf_trans_ident, LO_FREQUENCY );
1103             sound->set_volume( 0.3 );
1104             globals->get_soundmgr()->add( sound, "adf-ident" );
1105
1106             int offset = (int)(sg_random() * 30.0);
1107             adf_play_count = offset / 4;
1108             adf_last_time = globals->get_time_params()->get_cur_time() -
1109                 offset;
1110             // cout << "offset = " << offset << " play_count = "
1111             //      << adf_play_count << " adf_last_time = "
1112             //      << adf_last_time << " current time = "
1113             //      << globals->get_time_params()->get_cur_time() << endl;
1114 #endif
1115
1116             // cout << "Found an adf station in range" << endl;
1117             // cout << " id = " << nav.get_ident() << endl;
1118         }
1119     } else {
1120         adf_valid = false;
1121         adf_ident = "";
1122         adf_trans_ident = "";
1123 #ifdef ENABLE_AUDIO_SUPPORT
1124         globals->get_soundmgr()->remove( "adf-ident" );
1125 #endif
1126         last_adf_ident = "";
1127         // cout << "not picking up adf. :-(" << endl;
1128     }
1129 }
1130
1131
1132 // return the amount of heading needle deflection, returns a value
1133 // clamped to the range of ( -10 , 10 )
1134 double FGRadioStack::get_nav1_heading_needle_deflection() const {
1135     double r;
1136
1137     if ( nav1_inrange ) {
1138         r = nav1_heading - nav1_radial;
1139         // cout << "Radial = " << nav1_radial 
1140         //      << "  Bearing = " << nav1_heading << endl;
1141     
1142         while ( r >  180.0 ) { r -= 360.0;}
1143         while ( r < -180.0 ) { r += 360.0;}
1144         if ( fabs(r) > 90.0 ) {
1145             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1146             if ( nav1_loc ) {
1147                 r = -r;
1148             }
1149         }
1150
1151         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1152         if ( nav1_loc ) { r *= 4.0; }
1153         if ( r < -10.0 ) { r = -10.0; }
1154         if ( r >  10.0 ) { r = 10.0; }
1155     } else {
1156         r = 0.0;
1157     }
1158
1159     return r;
1160 }
1161
1162 // return the amount of heading needle deflection, returns a value
1163 // clamped to the range of ( -10 , 10 )
1164 double FGRadioStack::get_nav2_heading_needle_deflection() const {
1165     double r;
1166
1167     if ( nav2_inrange ) {
1168         r = nav2_heading - nav2_radial;
1169         // cout << "Radial = " << nav2_radial 
1170         //      << "  Bearing = " << nav2_heading << endl;
1171     
1172         while (r> 180.0) r-=360.0;
1173         while (r<-180.0) r+=360.0;
1174         if ( fabs(r) > 90.0 )
1175             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1176         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1177         if ( nav2_loc ) r *= 4.0;
1178         if ( r < -10.0 ) r = -10.0;
1179         if ( r > 10.0 ) r = 10.0;
1180     } else {
1181         r = 0.0;
1182     }
1183
1184     return r;
1185 }
1186
1187 // return the amount of glide slope needle deflection (.i.e. the
1188 // number of degrees we are off the glide slope * 5.0
1189 double FGRadioStack::get_nav1_gs_needle_deflection() const {
1190     if ( nav1_inrange && nav1_has_gs ) {
1191         double x = nav1_gs_dist;
1192         double y = (fgGetDouble("/position/altitude-ft") - nav1_elev)
1193             * SG_FEET_TO_METER;
1194         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1195         return (nav1_target_gs - angle) * 5.0;
1196     } else {
1197         return 0.0;
1198     }
1199 }
1200
1201
1202 // return the amount of glide slope needle deflection (.i.e. the
1203 // number of degrees we are off the glide slope * 5.0
1204 double FGRadioStack::get_nav2_gs_needle_deflection() const {
1205     if ( nav2_inrange && nav2_has_gs ) {
1206         double x = nav2_gs_dist;
1207         double y = (fgGetDouble("/position/altitude-ft") - nav2_elev)
1208             * SG_FEET_TO_METER;
1209         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1210         return (nav2_target_gs - angle) * 5.0;
1211     } else {
1212         return 0.0;
1213     }
1214 }
1215
1216
1217 /**
1218  * Return true if the NAV1 TO flag should be active.
1219  */
1220 bool 
1221 FGRadioStack::get_nav1_to_flag () const
1222 {
1223   if (nav1_inrange) {
1224     double offset = fabs(nav1_heading - nav1_radial);
1225     if (nav1_loc)
1226       return true;
1227     else
1228       return (offset <= 90.0 || offset >= 270.0);
1229   } else {
1230     return false;
1231   }
1232 }
1233
1234
1235 /**
1236  * Return true if the NAV1 FROM flag should be active.
1237  */
1238 bool
1239 FGRadioStack::get_nav1_from_flag () const
1240 {
1241   if (nav1_inrange) {
1242     double offset = fabs(nav1_heading - nav1_radial);
1243     if (nav1_loc)
1244       return false;
1245     else
1246       return (offset > 90.0 && offset < 270.0);
1247   } else {
1248     return false;
1249   }
1250 }
1251
1252
1253 /**
1254  * Return true if the NAV2 TO flag should be active.
1255  */
1256 bool 
1257 FGRadioStack::get_nav2_to_flag () const
1258 {
1259   if (nav2_inrange) {
1260     double offset = fabs(nav2_heading - nav2_radial);
1261     if (nav2_loc)
1262       return true;
1263     else
1264       return (offset <= 90.0 || offset >= 270.0);
1265   } else {
1266     return false;
1267   }
1268 }
1269
1270
1271 /**
1272  * Return true if the NAV2 FROM flag should be active.
1273  */
1274 bool
1275 FGRadioStack::get_nav2_from_flag () const
1276 {
1277   if (nav2_inrange) {
1278     double offset = fabs(nav2_heading - nav2_radial);
1279     if (nav2_loc)
1280       return false;
1281     else
1282       return (offset > 90.0 && offset < 270.0);
1283   } else {
1284     return false;
1285   }
1286 }
1287