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