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