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