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