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