]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/navradio.cxx
Add back power button binding, but move the location to /instrumentation/nav
[flightgear.git] / src / Instrumentation / navradio.cxx
1 // navradio.cxx -- class to manage a nav radio instance
2 //
3 // Written by Curtis Olson, started April 2000.
4 //
5 // Copyright (C) 2000 - 2002  Curtis L. Olson - http://www.flightgear.org/~curt
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 <iostream>
29 #include <string>
30 #include <sstream>
31
32 #include <simgear/compiler.h>
33 #include <simgear/sg_inlines.h>
34 #include <simgear/math/sg_random.h>
35 #include <simgear/math/vector.hxx>
36
37 #include <Aircraft/aircraft.hxx>
38 #include <Navaids/navlist.hxx>
39
40 #include "navradio.hxx"
41
42 #include <string>
43 SG_USING_STD(string);
44
45
46 // Constructor
47 FGNavRadio::FGNavRadio(SGPropertyNode *node) :
48     lon_node(fgGetNode("/position/longitude-deg", true)),
49     lat_node(fgGetNode("/position/latitude-deg", true)),
50     alt_node(fgGetNode("/position/altitude-ft", true)),
51     last_nav_id(""),
52     last_nav_vor(false),
53     nav_play_count(0),
54     nav_last_time(0),
55     need_update(true),
56     power_btn(true),
57     audio_btn(true),
58     nav_freq(0.0),
59     nav_alt_freq(0.0),
60     nav_heading(0.0),
61     nav_radial(0.0),
62     nav_target_radial(0.0),
63     nav_target_radial_true(0.0),
64     nav_target_auto_hdg(0.0),
65     nav_gs_rate_of_climb(0.0),
66     nav_vol_btn(0.0),
67     nav_ident_btn(true),
68     horiz_vel(0.0),
69     last_x(0.0),
70     name("nav"),
71     num(0)
72 {
73     SGPath path( globals->get_fg_root() );
74     SGPath term = path;
75     term.append( "Navaids/range.term" );
76     SGPath low = path;
77     low.append( "Navaids/range.low" );
78     SGPath high = path;
79     high.append( "Navaids/range.high" );
80
81     term_tbl = new SGInterpTable( term.str() );
82     low_tbl = new SGInterpTable( low.str() );
83     high_tbl = new SGInterpTable( high.str() );
84
85     int i;
86     for ( i = 0; i < node->nChildren(); ++i ) {
87         SGPropertyNode *child = node->getChild(i);
88         string cname = child->getName();
89         string cval = child->getStringValue();
90         if ( cname == "name" ) {
91             name = cval;
92         } else if ( cname == "number" ) {
93             num = child->getIntValue();
94         } else {
95             SG_LOG( SG_INSTR, SG_WARN, 
96                     "Error in nav radio config logic" );
97             if ( name.length() ) {
98                 SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
99             }
100         }
101     }
102
103 }
104
105
106 // Destructor
107 FGNavRadio::~FGNavRadio() 
108 {
109     delete term_tbl;
110     delete low_tbl;
111     delete high_tbl;
112 }
113
114
115 void
116 FGNavRadio::init ()
117 {
118     morse.init();
119
120     string branch;
121     branch = "/instrumentation/" + name;
122
123     SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
124
125     bus_power = 
126         fgGetNode(("/systems/electrical/outputs/" + name).c_str(), true);
127     nav_serviceable = node->getChild("serviceable", 0, true);
128     cdi_serviceable = (node->getChild("cdi", 0, true))
129         ->getChild("serviceable", 0, true);
130     gs_serviceable = (node->getChild("gs", 0, true))
131         ->getChild("serviceable");
132     tofrom_serviceable = (node->getChild("to-from", 0, true))
133         ->getChild("serviceable", 0, true);
134
135     std::ostringstream temp;
136     temp << name << "nav-ident" << num;
137     nav_fx_name = temp.str();
138     temp << name << "dme-ident" << num;
139     dme_fx_name = temp.str();
140 }
141
142 void
143 FGNavRadio::bind ()
144 {
145     std::ostringstream temp;
146     string branch;
147     temp << num;
148     branch = "/instrumentation/" + name + "[" + temp.str() + "]";
149
150                                 // User inputs
151     fgTie( (branch + "power-btn").c_str(), this,
152            &FGNavRadio::get_power_btn, &FGNavRadio::set_power_btn );
153     fgSetArchivable( (branch + "power-btn").c_str() );
154
155     fgTie( (branch + "/frequencies/selected-mhz").c_str() , this,
156           &FGNavRadio::get_nav_freq, &FGNavRadio::set_nav_freq );
157     fgSetArchivable( (branch + "/frequencies/selected-mhz").c_str() );
158
159     fgTie( (branch + "/frequencies/standby-mhz").c_str() , this,
160            &FGNavRadio::get_nav_alt_freq, &FGNavRadio::set_nav_alt_freq);
161     fgSetArchivable( (branch + "/frequencies/standby-mhz").c_str() );
162
163     fgTie( (branch + "/radials/selected-deg").c_str() , this,
164            &FGNavRadio::get_nav_sel_radial, &FGNavRadio::set_nav_sel_radial );
165     fgSetArchivable((branch + "/radials/selected-deg").c_str() );
166
167     fgTie( (branch + "/volume").c_str() , this,
168            &FGNavRadio::get_nav_vol_btn, &FGNavRadio::set_nav_vol_btn );
169     fgSetArchivable( (branch + "/volume").c_str() );
170
171     fgTie( (branch + "/ident").c_str(), this,
172            &FGNavRadio::get_nav_ident_btn, &FGNavRadio::set_nav_ident_btn );
173     fgSetArchivable( (branch + "/ident").c_str() );
174
175                                 // Radio outputs
176     fgTie( (branch + "/audio-btn").c_str(), this,
177            &FGNavRadio::get_audio_btn, &FGNavRadio::set_audio_btn );
178     fgSetArchivable( (branch + "/audio-btn").c_str() );
179
180     fgTie( (branch + "/heading-deg").c_str(),  
181            this, &FGNavRadio::get_nav_heading );
182
183     fgTie( (branch + "/radials/actual-deg").c_str(),
184            this, &FGNavRadio::get_nav_radial );
185
186     fgTie( (branch + "/radials/target-radial-deg").c_str(),
187            this, &FGNavRadio::get_nav_target_radial_true );
188
189     fgTie( (branch + "/radials/reciprocal-radial-deg").c_str(),
190            this, &FGNavRadio::get_nav_reciprocal_radial );
191
192     fgTie( (branch + "/radials/target-radial2-deg").c_str(),
193            this, &FGNavRadio::get_nav_target_radial );
194
195     fgTie( (branch + "/radials/target-auto-hdg-deg").c_str(),
196            this, &FGNavRadio::get_nav_target_auto_hdg );
197
198     fgTie( (branch + "/to-flag").c_str(),
199            this, &FGNavRadio::get_nav_to_flag );
200
201     fgTie( (branch + "/from-flag").c_str(),
202            this, &FGNavRadio::get_nav_from_flag );
203
204     fgTie( (branch + "/in-range").c_str(),
205            this, &FGNavRadio::get_nav_inrange );
206
207     fgTie( (branch + "/heading-needle-deflection").c_str(),
208            this, &FGNavRadio::get_nav_cdi_deflection );
209
210     fgTie( (branch + "/crosstrack-error-m").c_str(),
211            this, &FGNavRadio::get_nav_cdi_xtrack_error );
212
213     fgTie( (branch + "/has-gs").c_str(),
214            this, &FGNavRadio::get_nav_has_gs );
215
216     fgTie( (branch + "/nav-loc").c_str(),
217            this, &FGNavRadio::get_nav_loc );
218
219     fgTie( (branch + "/gs-rate-of-climb").c_str(),
220            this, &FGNavRadio::get_nav_gs_rate_of_climb );
221
222     fgTie( (branch + "/gs-needle-deflection").c_str(),
223            this, &FGNavRadio::get_nav_gs_deflection );
224
225     fgTie( (branch + "/gs-distance").c_str(),
226            this, &FGNavRadio::get_nav_gs_dist_signed );
227
228     fgTie( (branch + "/nav-distance").c_str(),
229            this, &FGNavRadio::get_nav_loc_dist );
230
231     fgTie( (branch + "/nav-id").c_str(),
232            this, &FGNavRadio::get_nav_id );
233
234     // put nav_id characters into seperate properties for instrument displays
235     fgTie( (branch + "/nav-id_asc1").c_str(),
236            this, &FGNavRadio::get_nav_id_c1 );
237
238     fgTie( (branch + "/nav-id_asc2").c_str(),
239            this, &FGNavRadio::get_nav_id_c2 );
240
241     fgTie( (branch + "/nav-id_asc3").c_str(),
242            this, &FGNavRadio::get_nav_id_c3 );
243
244     fgTie( (branch + "/nav-id_asc4").c_str(),
245            this, &FGNavRadio::get_nav_id_c4 );
246
247     // end of binding
248 }
249
250
251 void
252 FGNavRadio::unbind ()
253 {
254     std::ostringstream temp;
255     string branch;
256     temp << num;
257     branch = "/instrumentation/" + name + "[" + temp.str() + "]";
258
259     fgUntie( (branch + "/frequencies/selected-mhz").c_str() );
260     fgUntie( (branch + "/frequencies/standby-mhz").c_str() );
261     fgUntie( (branch + "/radials/actual-deg").c_str() );
262     fgUntie( (branch + "/radials/selected-deg").c_str() );
263     fgUntie( (branch + "/ident").c_str() );
264     fgUntie( (branch + "/to-flag").c_str() );
265     fgUntie( (branch + "/from-flag").c_str() );
266     fgUntie( (branch + "/in-range").c_str() );
267     fgUntie( (branch + "/heading-needle-deflection").c_str() );
268     fgUntie( (branch + "/gs-needle-deflection").c_str() );
269 }
270
271
272 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
273 double FGNavRadio::adjustNavRange( double stationElev, double aircraftElev,
274                                  double nominalRange )
275 {
276     // extend out actual usable range to be 1.3x the published safe range
277     const double usability_factor = 1.3;
278
279     // assumptions we model the standard service volume, plus
280     // ... rather than specifying a cylinder, we model a cone that
281     // contains the cylinder.  Then we put an upside down cone on top
282     // to model diminishing returns at too-high altitudes.
283
284     // altitude difference
285     double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
286     // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
287     //      << " station elev = " << stationElev << endl;
288
289     if ( nominalRange < 25.0 + SG_EPSILON ) {
290         // Standard Terminal Service Volume
291         return term_tbl->interpolate( alt ) * usability_factor;
292     } else if ( nominalRange < 50.0 + SG_EPSILON ) {
293         // Standard Low Altitude Service Volume
294         // table is based on range of 40, scale to actual range
295         return low_tbl->interpolate( alt ) * nominalRange / 40.0
296             * usability_factor;
297     } else {
298         // Standard High Altitude Service Volume
299         // table is based on range of 130, scale to actual range
300         return high_tbl->interpolate( alt ) * nominalRange / 130.0
301             * usability_factor;
302     }
303 }
304
305
306 // model standard ILS service volumes as per AIM 1-1-9
307 double FGNavRadio::adjustILSRange( double stationElev, double aircraftElev,
308                                  double offsetDegrees, double distance )
309 {
310     // assumptions we model the standard service volume, plus
311
312     // altitude difference
313     // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
314 //     double offset = fabs( offsetDegrees );
315
316 //     if ( offset < 10 ) {
317 //      return FG_ILS_DEFAULT_RANGE;
318 //     } else if ( offset < 35 ) {
319 //      return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
320 //     } else if ( offset < 45 ) {
321 //      return (45 - offset);
322 //     } else if ( offset > 170 ) {
323 //         return FG_ILS_DEFAULT_RANGE;
324 //     } else if ( offset > 145 ) {
325 //      return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
326 //     } else if ( offset > 135 ) {
327 //         return (offset - 135);
328 //     } else {
329 //      return 0;
330 //     }
331     return FG_LOC_DEFAULT_RANGE;
332 }
333
334
335 // Update the various nav values based on position and valid tuned in navs
336 void 
337 FGNavRadio::update(double dt) 
338 {
339     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
340     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
341     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
342
343     need_update = false;
344
345     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
346     Point3D station;
347     double az1, az2, s;
348
349     // On timeout, scan again
350     _time_before_search_sec -= dt;
351     if ( _time_before_search_sec < 0 ) {
352         search();
353     }
354
355     ////////////////////////////////////////////////////////////////////////
356     // Nav.
357     ////////////////////////////////////////////////////////////////////////
358
359     // cout << "nav_valid = " << nav_valid
360     //      << " power_btn = " << power_btn
361     //      << " bus_power = " << bus_power->getDoubleValue()
362     //      << " nav_serviceable = " << nav_serviceable->getBoolValue()
363     //      << endl;
364
365     if ( nav_valid && power_btn && (bus_power->getDoubleValue() > 1.0)
366          && nav_serviceable->getBoolValue() )
367     {
368         station = Point3D( nav_x, nav_y, nav_z );
369         nav_loc_dist = aircraft.distance3D( station );
370
371         if ( nav_has_gs ) {
372             // find closest distance to the gs base line
373             sgdVec3 p;
374             sgdSetVec3( p, aircraft.x(), aircraft.y(), aircraft.z() );
375             sgdVec3 p0;
376             sgdSetVec3( p0, nav_gs_x, nav_gs_y, nav_gs_z );
377             double dist = sgdClosestPointToLineDistSquared( p, p0,
378                                                             gs_base_vec );
379             nav_gs_dist = sqrt( dist );
380             // cout << "nav_gs_dist = " << nav_gs_dist << endl;
381
382             Point3D tmp( nav_gs_x, nav_gs_y, nav_gs_z );
383             // cout << " (" << aircraft.distance3D( tmp ) << ")" << endl;
384
385             // wgs84 heading to glide slope (to determine sign of distance)
386             geo_inverse_wgs_84( elev,
387                                 lat * SGD_RADIANS_TO_DEGREES,
388                                 lon * SGD_RADIANS_TO_DEGREES, 
389                                 nav_gslat, nav_gslon,
390                                 &az1, &az2, &s );
391             double r = az1 - nav_target_radial;
392             while ( r >  180.0 ) { r -= 360.0;}
393             while ( r < -180.0 ) { r += 360.0;}
394             if ( r >= -90.0 && r <= 90.0 ) {
395                 nav_gs_dist_signed = nav_gs_dist;
396             } else {
397                 nav_gs_dist_signed = -nav_gs_dist;
398             }
399             /* cout << "Target Radial = " << nav_target_radial 
400                  << "  Bearing = " << az1
401                  << "  dist (signed) = " << nav_gs_dist_signed
402                  << endl; */
403             
404         } else {
405             nav_gs_dist = 0.0;
406         }
407         
408         // wgs84 heading to localizer
409         geo_inverse_wgs_84( elev,
410                             lat * SGD_RADIANS_TO_DEGREES,
411                             lon * SGD_RADIANS_TO_DEGREES, 
412                             nav_loclat, nav_loclon,
413                             &nav_heading, &az2, &s );
414         // cout << "az1 = " << az1 << " magvar = " << nav_magvar << endl;
415         nav_radial = az2 - nav_twist;
416         // cout << " heading = " << nav_heading
417         //      << " dist = " << nav_dist << endl;
418
419         if ( nav_loc ) {
420             double offset = nav_radial - nav_target_radial;
421             while ( offset < -180.0 ) { offset += 360.0; }
422             while ( offset > 180.0 ) { offset -= 360.0; }
423             // cout << "ils offset = " << offset << endl;
424             nav_effective_range
425                 = adjustILSRange( nav_elev, elev, offset,
426                                   nav_loc_dist * SG_METER_TO_NM );
427         } else {
428             nav_effective_range = adjustNavRange( nav_elev, elev, nav_range );
429         }
430         // cout << "nav range = " << nav_effective_range
431         //      << " (" << nav_range << ")" << endl;
432
433         if ( nav_loc_dist < nav_effective_range * SG_NM_TO_METER ) {
434             nav_inrange = true;
435         } else if ( nav_loc_dist < 2 * nav_effective_range * SG_NM_TO_METER ) {
436             nav_inrange = sg_random() < 
437                 ( 2 * nav_effective_range * SG_NM_TO_METER - nav_loc_dist ) /
438                 (nav_effective_range * SG_NM_TO_METER);
439         } else {
440             nav_inrange = false;
441         }
442
443         if ( !nav_loc ) {
444             nav_target_radial = nav_sel_radial;
445         }
446
447         // Calculate some values for the nav/ils hold autopilot
448
449         double cur_radial = get_nav_reciprocal_radial();
450         if ( nav_loc ) {
451             // ILS localizers radials are already "true" in our
452             // database
453         } else {
454             cur_radial += nav_twist;
455         }
456         if ( get_nav_from_flag() ) {
457             cur_radial += 180.0;
458             while ( cur_radial >= 360.0 ) { cur_radial -= 360.0; }
459         }
460         
461         // AUTOPILOT HELPERS
462
463         // determine the target radial in "true" heading
464         nav_target_radial_true = nav_target_radial;
465         if ( nav_loc ) {
466             // ILS localizers radials are already "true" in our
467             // database
468         } else {
469             // VOR radials need to have that vor's offset added in
470             nav_target_radial_true += nav_twist;
471         }
472
473         while ( nav_target_radial_true < 0.0 ) {
474             nav_target_radial_true += 360.0;
475         }
476         while ( nav_target_radial_true > 360.0 ) {
477             nav_target_radial_true -= 360.0;
478         }
479
480         // determine the heading adjustment needed.
481         // over 8km scale by 3.0 
482         //    (3 is chosen because max deflection is 10
483         //    and 30 is clamped angle to radial)
484         // under 8km scale by 10.0
485         //    because the overstated error helps drive it to the radial in a 
486         //    moderate cross wind.
487         double adjustment = 0.0;
488         if (nav_loc_dist > 8000) {
489             adjustment = get_nav_cdi_deflection() * 3.0;
490         } else {
491             adjustment = get_nav_cdi_deflection() * 10.0;
492         }
493         SG_CLAMP_RANGE( adjustment, -30.0, 30.0 );
494         
495         // determine the target heading to fly to intercept the
496         // tgt_radial
497         nav_target_auto_hdg = nav_target_radial_true + adjustment; 
498         while ( nav_target_auto_hdg <   0.0 ) { nav_target_auto_hdg += 360.0; }
499         while ( nav_target_auto_hdg > 360.0 ) { nav_target_auto_hdg -= 360.0; }
500
501         // cross track error
502         // ????
503
504         // Calculate desired rate of climb for intercepting the GS
505         double x = nav_gs_dist;
506         double y = (alt_node->getDoubleValue() - nav_elev)
507             * SG_FEET_TO_METER;
508         double current_angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
509
510         double target_angle = nav_target_gs;
511         double gs_diff = target_angle - current_angle;
512
513         // convert desired vertical path angle into a climb rate
514         double des_angle = current_angle - 10 * gs_diff;
515
516         // estimate horizontal speed towards ILS in meters per minute
517         double dist = last_x - x;
518         last_x = x;
519         if ( dt > 0.0 ) {
520             // avoid nan
521             double new_vel = ( dist / dt );
522  
523             horiz_vel = 0.75 * horiz_vel + 0.25 * new_vel;
524             // double horiz_vel = cur_fdm_state->get_V_ground_speed()
525             //    * SG_FEET_TO_METER * 60.0;
526             // double horiz_vel = airspeed_node->getFloatValue()
527             //    * SG_FEET_TO_METER * 60.0;
528
529             nav_gs_rate_of_climb = -sin( des_angle * SGD_DEGREES_TO_RADIANS )
530                 * horiz_vel * SG_METER_TO_FEET;
531         }
532     } else {
533         nav_inrange = false;
534         // cout << "not picking up vor. :-(" << endl;
535     }
536
537     if ( nav_valid && nav_inrange && nav_serviceable->getBoolValue() ) {
538         // play station ident via audio system if on + ident,
539         // otherwise turn it off
540         if ( power_btn && (bus_power->getDoubleValue() > 1.0)
541              && nav_ident_btn && audio_btn )
542         {
543             SGSoundSample *sound;
544             sound = globals->get_soundmgr()->find( nav_fx_name );
545             if ( sound != NULL ) {
546                 sound->set_volume( nav_vol_btn );
547             } else {
548                 SG_LOG( SG_COCKPIT, SG_ALERT,
549                         "Can't find nav-vor-ident sound" );
550             }
551             sound = globals->get_soundmgr()->find( dme_fx_name );
552             if ( sound != NULL ) {
553                 sound->set_volume( nav_vol_btn );
554             } else {
555                 SG_LOG( SG_COCKPIT, SG_ALERT,
556                         "Can't find nav-dme-ident sound" );
557             }
558             // cout << "nav_last_time = " << nav_last_time << " ";
559             // cout << "cur_time = "
560             //      << globals->get_time_params()->get_cur_time();
561             if ( nav_last_time <
562                  globals->get_time_params()->get_cur_time() - 30 ) {
563                 nav_last_time = globals->get_time_params()->get_cur_time();
564                 nav_play_count = 0;
565             }
566             // cout << " nav_play_count = " << nav_play_count << endl;
567             // cout << "playing = "
568             //      << globals->get_soundmgr()->is_playing(nav_fx_name)
569             //      << endl;
570             if ( nav_play_count < 4 ) {
571                 // play VOR ident
572                 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) ) {
573                     globals->get_soundmgr()->play_once( nav_fx_name );
574                     ++nav_play_count;
575                 }
576             } else if ( nav_play_count < 5 && nav_has_dme ) {
577                 // play DME ident
578                 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) &&
579                      !globals->get_soundmgr()->is_playing(dme_fx_name) ) {
580                     globals->get_soundmgr()->play_once( dme_fx_name );
581                     ++nav_play_count;
582                 }
583             }
584         } else {
585             globals->get_soundmgr()->stop( nav_fx_name );
586             globals->get_soundmgr()->stop( dme_fx_name );
587         }
588     }
589 }
590
591
592 // Update current nav/adf radio stations based on current postition
593 void FGNavRadio::search() 
594 {
595
596     // reset search time
597     _time_before_search_sec = 1.0;
598
599     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
600     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
601     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
602
603     FGNavRecord *nav = NULL;
604     FGNavRecord *loc = NULL;
605     FGNavRecord *dme = NULL;
606     FGNavRecord *gs = NULL;
607
608     ////////////////////////////////////////////////////////////////////////
609     // Nav.
610     ////////////////////////////////////////////////////////////////////////
611
612     nav = globals->get_navlist()->findByFreq(nav_freq, lon, lat, elev);
613     dme = globals->get_dmelist()->findByFreq(nav_freq, lon, lat, elev);
614     if ( nav == NULL ) {
615         loc = globals->get_loclist()->findByFreq(nav_freq, lon, lat, elev);
616         gs = globals->get_gslist()->findByFreq(nav_freq, lon, lat, elev);
617     }
618         
619     if ( loc != NULL ) {
620         nav_id = loc->get_ident();
621         // cout << "localizer = " << nav_id << endl;
622         nav_valid = true;
623         if ( last_nav_id != nav_id || last_nav_vor ) {
624             nav_trans_ident = loc->get_trans_ident();
625             nav_target_radial = loc->get_multiuse();
626             while ( nav_target_radial <   0.0 ) { nav_target_radial += 360.0; }
627             while ( nav_target_radial > 360.0 ) { nav_target_radial -= 360.0; }
628             nav_loclon = loc->get_lon();
629             nav_loclat = loc->get_lat();
630             nav_x = loc->get_x();
631             nav_y = loc->get_y();
632             nav_z = loc->get_z();
633             last_nav_id = nav_id;
634             last_nav_vor = false;
635             nav_loc = true;
636             nav_has_dme = (dme != NULL);
637             nav_has_gs = (gs != NULL);
638             if ( nav_has_gs ) {
639                 nav_gslon = gs->get_lon();
640                 nav_gslat = gs->get_lat();
641                 nav_elev = gs->get_elev_ft();
642                 int tmp = (int)(gs->get_multiuse() / 1000.0);
643                 nav_target_gs = (double)tmp / 100.0;
644                 nav_gs_x = gs->get_x();
645                 nav_gs_y = gs->get_y();
646                 nav_gs_z = gs->get_z();
647
648                 // derive GS baseline (perpendicular to the runay
649                 // along the ground)
650                 double tlon, tlat, taz;
651                 geo_direct_wgs_84 ( 0.0, nav_gslat, nav_gslon,
652                                     nav_target_radial + 90,  
653                                     100.0, &tlat, &tlon, &taz );
654                 // cout << "nav_target_radial = " << nav_target_radial << endl;
655                 // cout << "nav_loc = " << nav_loc << endl;
656                 // cout << nav_gslon << "," << nav_gslat << "  "
657                 //      << tlon << "," << tlat << "  (" << nav_elev << ")"
658                 //      << endl;
659                 Point3D p1 = sgGeodToCart( Point3D(tlon*SGD_DEGREES_TO_RADIANS,
660                                                    tlat*SGD_DEGREES_TO_RADIANS,
661                                                    nav_elev*SG_FEET_TO_METER)
662                                            );
663                 // cout << nav_gs_x << "," << nav_gs_y << "," << nav_gs_z
664                 //      << endl;
665                 // cout << p1 << endl;
666                 sgdSetVec3( gs_base_vec,
667                             p1.x()-nav_gs_x, p1.y()-nav_gs_y, p1.z()-nav_gs_z );
668                 // cout << gs_base_vec[0] << "," << gs_base_vec[1] << ","
669                 //      << gs_base_vec[2] << endl;
670             } else {
671                 nav_elev = loc->get_elev_ft();
672             }
673             nav_twist = 0;
674             nav_range = FG_LOC_DEFAULT_RANGE;
675             nav_effective_range = nav_range;
676
677             if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
678                 globals->get_soundmgr()->remove( nav_fx_name );
679             }
680             SGSoundSample *sound;
681             sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
682             sound->set_volume( 0.3 );
683             globals->get_soundmgr()->add( sound, nav_fx_name );
684
685             if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
686                 globals->get_soundmgr()->remove( dme_fx_name );
687             }
688             sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
689             sound->set_volume( 0.3 );
690             globals->get_soundmgr()->add( sound, dme_fx_name );
691
692             int offset = (int)(sg_random() * 30.0);
693             nav_play_count = offset / 4;
694             nav_last_time = globals->get_time_params()->get_cur_time() -
695                 offset;
696             // cout << "offset = " << offset << " play_count = "
697             //      << nav_play_count
698             //      << " nav_last_time = " << nav_last_time
699             //      << " current time = "
700             //      << globals->get_time_params()->get_cur_time() << endl;
701
702             // cout << "Found an loc station in range" << endl;
703             // cout << " id = " << loc->get_locident() << endl;
704         }
705     } else if ( nav != NULL ) {
706         nav_id = nav->get_ident();
707         // cout << "nav = " << nav_id << endl;
708         nav_valid = true;
709         if ( last_nav_id != nav_id || !last_nav_vor ) {
710             last_nav_id = nav_id;
711             last_nav_vor = true;
712             nav_trans_ident = nav->get_trans_ident();
713             nav_loc = false;
714             nav_has_dme = (dme != NULL);
715             nav_has_gs = false;
716             nav_loclon = nav->get_lon();
717             nav_loclat = nav->get_lat();
718             nav_elev = nav->get_elev_ft();
719             nav_twist = nav->get_multiuse();
720             nav_range = nav->get_range();
721             nav_effective_range = adjustNavRange(nav_elev, elev, nav_range);
722             nav_target_gs = 0.0;
723             nav_target_radial = nav_sel_radial;
724             nav_x = nav->get_x();
725             nav_y = nav->get_y();
726             nav_z = nav->get_z();
727
728             if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
729                 globals->get_soundmgr()->remove( nav_fx_name );
730             }
731             SGSoundSample *sound;
732             sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
733             sound->set_volume( 0.3 );
734             if ( globals->get_soundmgr()->add( sound, nav_fx_name ) ) {
735                 // cout << "Added nav-vor-ident sound" << endl;
736             } else {
737                 SG_LOG(SG_COCKPIT, SG_WARN, "Failed to add v1-vor-ident sound");
738             }
739
740             if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
741                 globals->get_soundmgr()->remove( dme_fx_name );
742             }
743             sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
744             sound->set_volume( 0.3 );
745             globals->get_soundmgr()->add( sound, dme_fx_name );
746
747             int offset = (int)(sg_random() * 30.0);
748             nav_play_count = offset / 4;
749             nav_last_time = globals->get_time_params()->get_cur_time() -
750                 offset;
751             // cout << "offset = " << offset << " play_count = "
752             //      << nav_play_count << " nav_last_time = "
753             //      << nav_last_time << " current time = "
754             //      << globals->get_time_params()->get_cur_time() << endl;
755
756             // cout << "Found a vor station in range" << endl;
757             // cout << " id = " << nav->get_ident() << endl;
758         }
759     } else {
760         nav_valid = false;
761         nav_id = "";
762         nav_target_radial = 0;
763         nav_trans_ident = "";
764         last_nav_id = "";
765         if ( ! globals->get_soundmgr()->remove( nav_fx_name ) ) {
766             SG_LOG(SG_COCKPIT, SG_WARN, "Failed to remove nav-vor-ident sound");
767         }
768         globals->get_soundmgr()->remove( dme_fx_name );
769         // cout << "not picking up vor1. :-(" << endl;
770     }
771 }
772
773
774 // return the amount of heading needle deflection, returns a value
775 // clamped to the range of ( -10 , 10 )
776 double FGNavRadio::get_nav_cdi_deflection() const {
777     double r;
778
779     if ( nav_inrange
780          && nav_serviceable->getBoolValue() && cdi_serviceable->getBoolValue() )
781     {
782         r = nav_radial - nav_target_radial;
783         // cout << "Target radial = " << nav_target_radial 
784         //      << "  Actual radial = " << nav_radial << endl;
785     
786         while ( r >  180.0 ) { r -= 360.0;}
787         while ( r < -180.0 ) { r += 360.0;}
788         if ( fabs(r) > 90.0 )
789             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
790
791         // According to Robin Peel, the ILS is 4x more sensitive than a vor
792         r = -r;                 // reverse, since radial is outbound
793         if ( nav_loc ) { r *= 4.0; }
794         if ( r < -10.0 ) { r = -10.0; }
795         if ( r >  10.0 ) { r = 10.0; }
796     } else {
797         r = 0.0;
798     }
799
800     return r;
801 }
802
803 // return the amount of cross track distance error, returns a meters
804 double FGNavRadio::get_nav_cdi_xtrack_error() const {
805     double r, m;
806
807     if ( nav_inrange
808          && nav_serviceable->getBoolValue() && cdi_serviceable->getBoolValue() )
809     {
810         r = nav_radial - nav_target_radial;
811         // cout << "Target radial = " << nav_target_radial 
812         //     << "  Actual radial = " << nav_radial
813         //     << "  r = " << r << endl;
814     
815         while ( r >  180.0 ) { r -= 360.0;}
816         while ( r < -180.0 ) { r += 360.0;}
817         if ( fabs(r) > 90.0 )
818             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
819
820         r = -r;                 // reverse, since radial is outbound
821
822         m = nav_loc_dist * sin(r * SGD_DEGREES_TO_RADIANS);
823
824     } else {
825         m = 0.0;
826     }
827
828     return m;
829 }
830
831 // return the amount of glide slope needle deflection (.i.e. the
832 // number of degrees we are off the glide slope * 5.0
833 double FGNavRadio::get_nav_gs_deflection() const {
834     if ( nav_inrange && nav_has_gs
835          && nav_serviceable->getBoolValue() && gs_serviceable->getBoolValue() )
836     {
837         double x = nav_gs_dist;
838         double y = (fgGetDouble("/position/altitude-ft") - nav_elev)
839             * SG_FEET_TO_METER;
840         // cout << "dist = " << x << " height = " << y << endl;
841         double angle = asin( y / x ) * SGD_RADIANS_TO_DEGREES;
842         return (nav_target_gs - angle) * 5.0;
843     } else {
844         return 0.0;
845     }
846 }
847
848
849 /**
850  * Return true if the NAV TO flag should be active.
851  */
852 bool 
853 FGNavRadio::get_nav_to_flag () const
854 {
855     if ( nav_inrange
856          && nav_serviceable->getBoolValue()
857          && tofrom_serviceable->getBoolValue() )
858     {
859         double offset = fabs(nav_radial - nav_target_radial);
860         if (nav_loc) {
861             return true;
862         } else {
863             return !(offset <= 90.0 || offset >= 270.0);
864         }
865     } else {
866         return false;
867     }
868 }
869
870
871 /**
872  * Return true if the NAV FROM flag should be active.
873  */
874 bool
875 FGNavRadio::get_nav_from_flag () const
876 {
877     if ( nav_inrange
878          && nav_serviceable->getBoolValue()
879          && tofrom_serviceable->getBoolValue() ) {
880         double offset = fabs(nav_radial - nav_target_radial);
881         if (nav_loc) {
882             return false;
883         } else {
884           return !(offset > 90.0 && offset < 270.0);
885         }
886     } else {
887         return false;
888     }
889 }
890
891
892 /**
893  * Return the true heading to station
894  */
895 double
896 FGNavRadio::get_nav_heading () const
897 {
898     return nav_heading;
899 }
900
901
902 /**
903  * Return the current radial.
904  */
905 double
906 FGNavRadio::get_nav_radial () const
907 {
908     return nav_radial;
909 }
910
911 double
912 FGNavRadio::get_nav_reciprocal_radial () const
913 {
914     double recip = nav_radial + 180;
915     if ( recip >= 360 ) {
916         recip -= 360;
917     }
918     return recip;
919 }