1 // navradio.cxx -- class to manage a nav radio instance
3 // Written by Curtis Olson, started April 2000.
5 // Copyright (C) 2000 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
32 #include <simgear/compiler.h>
33 #include <simgear/sg_inlines.h>
34 #include <simgear/math/sg_random.h>
35 #include <simgear/math/vector.hxx>
37 #include <Aircraft/aircraft.hxx>
38 #include <Navaids/navlist.hxx>
40 #include "navradio.hxx"
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)),
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),
74 _time_before_search_sec(-1.0)
76 SGPath path( globals->get_fg_root() );
78 term.append( "Navaids/range.term" );
80 low.append( "Navaids/range.low" );
82 high.append( "Navaids/range.high" );
84 term_tbl = new SGInterpTable( term.str() );
85 low_tbl = new SGInterpTable( low.str() );
86 high_tbl = new SGInterpTable( high.str() );
89 for ( i = 0; i < node->nChildren(); ++i ) {
90 SGPropertyNode *child = node->getChild(i);
91 string cname = child->getName();
92 string cval = child->getStringValue();
93 if ( cname == "name" ) {
95 } else if ( cname == "number" ) {
96 num = child->getIntValue();
98 SG_LOG( SG_INSTR, SG_WARN,
99 "Error in nav radio config logic" );
100 if ( name.length() ) {
101 SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
110 FGNavRadio::~FGNavRadio()
124 branch = "/instrumentation/" + name;
126 SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
129 fgGetNode(("/systems/electrical/outputs/" + name).c_str(), true);
130 nav_serviceable = node->getChild("serviceable", 0, true);
131 cdi_serviceable = (node->getChild("cdi", 0, true))
132 ->getChild("serviceable", 0, true);
133 gs_serviceable = (node->getChild("gs", 0, true))
134 ->getChild("serviceable");
135 tofrom_serviceable = (node->getChild("to-from", 0, true))
136 ->getChild("serviceable", 0, true);
137 nav_slaved_to_gps = node->getChild("slaved-to-gps", 0, true);
139 gps_cdi_deflection = fgGetNode("/instrumentation/gps/cdi-deflection", true);
140 gps_to_flag = fgGetNode("/instrumentation/gps/to-flag", true);
142 std::ostringstream temp;
143 temp << name << "nav-ident" << num;
144 nav_fx_name = temp.str();
145 temp << name << "dme-ident" << num;
146 dme_fx_name = temp.str();
152 std::ostringstream temp;
155 branch = "/instrumentation/" + name + "[" + temp.str() + "]";
158 fgTie( (branch + "power-btn").c_str(), this,
159 &FGNavRadio::get_power_btn, &FGNavRadio::set_power_btn );
160 fgSetArchivable( (branch + "power-btn").c_str() );
162 fgTie( (branch + "/frequencies/selected-mhz").c_str() , this,
163 &FGNavRadio::get_nav_freq, &FGNavRadio::set_nav_freq );
164 fgSetArchivable( (branch + "/frequencies/selected-mhz").c_str() );
166 fgTie( (branch + "/frequencies/standby-mhz").c_str() , this,
167 &FGNavRadio::get_nav_alt_freq, &FGNavRadio::set_nav_alt_freq);
168 fgSetArchivable( (branch + "/frequencies/standby-mhz").c_str() );
170 fgTie( (branch + "/frequencies/selected-mhz-fmt").c_str() , this,
171 &FGNavRadio::get_fmt_freq, &FGNavRadio::set_fmt_freq );
172 fgSetArchivable( (branch + "/frequencies/selected-mhz-fmt").c_str() );
174 fgTie( (branch + "/frequencies/standby-mhz-fmt").c_str() , this,
175 &FGNavRadio::get_fmt_alt_freq, &FGNavRadio::set_fmt_alt_freq);
176 fgSetArchivable( (branch + "/frequencies/standby-mhz-fmt").c_str() );
178 fgTie( (branch + "/radials/selected-deg").c_str() , this,
179 &FGNavRadio::get_nav_sel_radial, &FGNavRadio::set_nav_sel_radial );
180 fgSetArchivable((branch + "/radials/selected-deg").c_str() );
182 fgTie( (branch + "/volume").c_str() , this,
183 &FGNavRadio::get_nav_vol_btn, &FGNavRadio::set_nav_vol_btn );
184 fgSetArchivable( (branch + "/volume").c_str() );
186 fgTie( (branch + "/ident").c_str(), this,
187 &FGNavRadio::get_nav_ident_btn, &FGNavRadio::set_nav_ident_btn );
188 fgSetArchivable( (branch + "/ident").c_str() );
191 fgTie( (branch + "/audio-btn").c_str(), this,
192 &FGNavRadio::get_audio_btn, &FGNavRadio::set_audio_btn );
193 fgSetArchivable( (branch + "/audio-btn").c_str() );
195 fgTie( (branch + "/heading-deg").c_str(),
196 this, &FGNavRadio::get_nav_heading );
198 fgTie( (branch + "/radials/actual-deg").c_str(),
199 this, &FGNavRadio::get_nav_radial );
201 fgTie( (branch + "/radials/target-radial-deg").c_str(),
202 this, &FGNavRadio::get_nav_target_radial_true );
204 fgTie( (branch + "/radials/reciprocal-radial-deg").c_str(),
205 this, &FGNavRadio::get_nav_reciprocal_radial );
207 fgTie( (branch + "/radials/target-auto-hdg-deg").c_str(),
208 this, &FGNavRadio::get_nav_target_auto_hdg );
210 fgTie( (branch + "/to-flag").c_str(),
211 this, &FGNavRadio::get_nav_to_flag );
213 fgTie( (branch + "/from-flag").c_str(),
214 this, &FGNavRadio::get_nav_from_flag );
216 fgTie( (branch + "/in-range").c_str(),
217 this, &FGNavRadio::get_nav_inrange );
219 fgTie( (branch + "/heading-needle-deflection").c_str(),
220 this, &FGNavRadio::get_nav_cdi_deflection );
222 fgTie( (branch + "/crosstrack-error-m").c_str(),
223 this, &FGNavRadio::get_nav_cdi_xtrack_error );
225 fgTie( (branch + "/has-gs").c_str(),
226 this, &FGNavRadio::get_nav_has_gs );
228 fgTie( (branch + "/nav-loc").c_str(),
229 this, &FGNavRadio::get_nav_loc );
231 fgTie( (branch + "/gs-rate-of-climb").c_str(),
232 this, &FGNavRadio::get_nav_gs_rate_of_climb );
234 fgTie( (branch + "/gs-needle-deflection").c_str(),
235 this, &FGNavRadio::get_nav_gs_deflection );
237 fgTie( (branch + "/gs-distance").c_str(),
238 this, &FGNavRadio::get_nav_gs_dist_signed );
240 fgTie( (branch + "/nav-distance").c_str(),
241 this, &FGNavRadio::get_nav_loc_dist );
243 fgTie( (branch + "/nav-id").c_str(),
244 this, &FGNavRadio::get_nav_id );
246 // put nav_id characters into seperate properties for instrument displays
247 fgTie( (branch + "/nav-id_asc1").c_str(),
248 this, &FGNavRadio::get_nav_id_c1 );
250 fgTie( (branch + "/nav-id_asc2").c_str(),
251 this, &FGNavRadio::get_nav_id_c2 );
253 fgTie( (branch + "/nav-id_asc3").c_str(),
254 this, &FGNavRadio::get_nav_id_c3 );
256 fgTie( (branch + "/nav-id_asc4").c_str(),
257 this, &FGNavRadio::get_nav_id_c4 );
264 FGNavRadio::unbind ()
266 std::ostringstream temp;
269 branch = "/instrumentation/" + name + "[" + temp.str() + "]";
271 fgUntie( (branch + "/frequencies/selected-mhz").c_str() );
272 fgUntie( (branch + "/frequencies/standby-mhz").c_str() );
273 fgUntie( (branch + "/radials/actual-deg").c_str() );
274 fgUntie( (branch + "/radials/selected-deg").c_str() );
275 fgUntie( (branch + "/ident").c_str() );
276 fgUntie( (branch + "/to-flag").c_str() );
277 fgUntie( (branch + "/from-flag").c_str() );
278 fgUntie( (branch + "/in-range").c_str() );
279 fgUntie( (branch + "/heading-needle-deflection").c_str() );
280 fgUntie( (branch + "/gs-needle-deflection").c_str() );
284 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
285 double FGNavRadio::adjustNavRange( double stationElev, double aircraftElev,
286 double nominalRange )
288 // extend out actual usable range to be 1.3x the published safe range
289 const double usability_factor = 1.3;
291 // assumptions we model the standard service volume, plus
292 // ... rather than specifying a cylinder, we model a cone that
293 // contains the cylinder. Then we put an upside down cone on top
294 // to model diminishing returns at too-high altitudes.
296 // altitude difference
297 double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
298 // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
299 // << " station elev = " << stationElev << endl;
301 if ( nominalRange < 25.0 + SG_EPSILON ) {
302 // Standard Terminal Service Volume
303 return term_tbl->interpolate( alt ) * usability_factor;
304 } else if ( nominalRange < 50.0 + SG_EPSILON ) {
305 // Standard Low Altitude Service Volume
306 // table is based on range of 40, scale to actual range
307 return low_tbl->interpolate( alt ) * nominalRange / 40.0
310 // Standard High Altitude Service Volume
311 // table is based on range of 130, scale to actual range
312 return high_tbl->interpolate( alt ) * nominalRange / 130.0
318 // model standard ILS service volumes as per AIM 1-1-9
319 double FGNavRadio::adjustILSRange( double stationElev, double aircraftElev,
320 double offsetDegrees, double distance )
322 // assumptions we model the standard service volume, plus
324 // altitude difference
325 // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
326 // double offset = fabs( offsetDegrees );
328 // if ( offset < 10 ) {
329 // return FG_ILS_DEFAULT_RANGE;
330 // } else if ( offset < 35 ) {
331 // return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
332 // } else if ( offset < 45 ) {
333 // return (45 - offset);
334 // } else if ( offset > 170 ) {
335 // return FG_ILS_DEFAULT_RANGE;
336 // } else if ( offset > 145 ) {
337 // return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
338 // } else if ( offset > 135 ) {
339 // return (offset - 135);
343 return FG_LOC_DEFAULT_RANGE;
347 // Update the various nav values based on position and valid tuned in navs
349 FGNavRadio::update(double dt)
351 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
352 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
353 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
357 Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
361 // Create "formatted" versions of the nav frequencies for
362 // consistant display output.
364 sprintf( tmp, "%.2f", nav_freq );
366 sprintf( tmp, "%.2f", nav_alt_freq );
369 // On timeout, scan again
370 _time_before_search_sec -= dt;
371 if ( _time_before_search_sec < 0 ) {
375 ////////////////////////////////////////////////////////////////////////
377 ////////////////////////////////////////////////////////////////////////
379 // cout << "nav_valid = " << nav_valid
380 // << " power_btn = " << power_btn
381 // << " bus_power = " << bus_power->getDoubleValue()
382 // << " nav_serviceable = " << nav_serviceable->getBoolValue()
385 if ( nav_valid && power_btn && (bus_power->getDoubleValue() > 1.0)
386 && nav_serviceable->getBoolValue() )
388 station = Point3D( nav_x, nav_y, nav_z );
389 nav_loc_dist = aircraft.distance3D( station );
390 // cout << "station = " << station << " dist = " << nav_loc_dist
394 // find closest distance to the gs base line
396 sgdSetVec3( p, aircraft.x(), aircraft.y(), aircraft.z() );
398 sgdSetVec3( p0, nav_gs_x, nav_gs_y, nav_gs_z );
399 double dist = sgdClosestPointToLineDistSquared( p, p0,
401 nav_gs_dist = sqrt( dist );
402 // cout << "nav_gs_dist = " << nav_gs_dist << endl;
404 Point3D tmp( nav_gs_x, nav_gs_y, nav_gs_z );
405 // cout << " (" << aircraft.distance3D( tmp ) << ")" << endl;
407 // wgs84 heading to glide slope (to determine sign of distance)
408 geo_inverse_wgs_84( elev,
409 lat * SGD_RADIANS_TO_DEGREES,
410 lon * SGD_RADIANS_TO_DEGREES,
411 nav_gslat, nav_gslon,
413 double r = az1 - nav_target_radial;
414 while ( r > 180.0 ) { r -= 360.0;}
415 while ( r < -180.0 ) { r += 360.0;}
416 if ( r >= -90.0 && r <= 90.0 ) {
417 nav_gs_dist_signed = nav_gs_dist;
419 nav_gs_dist_signed = -nav_gs_dist;
421 /* cout << "Target Radial = " << nav_target_radial
422 << " Bearing = " << az1
423 << " dist (signed) = " << nav_gs_dist_signed
430 // wgs84 heading to localizer
431 geo_inverse_wgs_84( elev,
432 lat * SGD_RADIANS_TO_DEGREES,
433 lon * SGD_RADIANS_TO_DEGREES,
434 nav_loclat, nav_loclon,
435 &nav_heading, &az2, &s );
436 // cout << "az1 = " << az1 << " magvar = " << nav_magvar << endl;
437 nav_radial = az2 - nav_twist;
438 // cout << " heading = " << nav_heading
439 // << " dist = " << nav_dist << endl;
442 double offset = nav_radial - nav_target_radial;
443 while ( offset < -180.0 ) { offset += 360.0; }
444 while ( offset > 180.0 ) { offset -= 360.0; }
445 // cout << "ils offset = " << offset << endl;
447 = adjustILSRange( nav_elev, elev, offset,
448 nav_loc_dist * SG_METER_TO_NM );
450 nav_effective_range = adjustNavRange( nav_elev, elev, nav_range );
452 // cout << "nav range = " << nav_effective_range
453 // << " (" << nav_range << ")" << endl;
455 if ( nav_loc_dist < nav_effective_range * SG_NM_TO_METER ) {
457 } else if ( nav_loc_dist < 2 * nav_effective_range * SG_NM_TO_METER ) {
458 nav_inrange = sg_random() <
459 ( 2 * nav_effective_range * SG_NM_TO_METER - nav_loc_dist ) /
460 (nav_effective_range * SG_NM_TO_METER);
466 nav_target_radial = nav_sel_radial;
469 // Calculate some values for the nav/ils hold autopilot
471 double cur_radial = get_nav_reciprocal_radial();
473 // ILS localizers radials are already "true" in our
476 cur_radial += nav_twist;
478 if ( get_nav_from_flag() ) {
480 while ( cur_radial >= 360.0 ) { cur_radial -= 360.0; }
485 // determine the target radial in "true" heading
486 nav_target_radial_true = nav_target_radial;
488 // ILS localizers radials are already "true" in our
491 // VOR radials need to have that vor's offset added in
492 nav_target_radial_true += nav_twist;
495 while ( nav_target_radial_true < 0.0 ) {
496 nav_target_radial_true += 360.0;
498 while ( nav_target_radial_true > 360.0 ) {
499 nav_target_radial_true -= 360.0;
502 // determine the heading adjustment needed.
503 // over 8km scale by 3.0
504 // (3 is chosen because max deflection is 10
505 // and 30 is clamped angle to radial)
506 // under 8km scale by 10.0
507 // because the overstated error helps drive it to the radial in a
508 // moderate cross wind.
509 double adjustment = 0.0;
510 if (nav_loc_dist > 8000) {
511 adjustment = get_nav_cdi_deflection() * 3.0;
513 adjustment = get_nav_cdi_deflection() * 10.0;
515 SG_CLAMP_RANGE( adjustment, -30.0, 30.0 );
517 // determine the target heading to fly to intercept the
519 nav_target_auto_hdg = nav_target_radial_true + adjustment;
520 while ( nav_target_auto_hdg < 0.0 ) { nav_target_auto_hdg += 360.0; }
521 while ( nav_target_auto_hdg > 360.0 ) { nav_target_auto_hdg -= 360.0; }
526 // Calculate desired rate of climb for intercepting the GS
527 double x = nav_gs_dist;
528 double y = (alt_node->getDoubleValue() - nav_elev)
530 double current_angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
532 double target_angle = nav_target_gs;
533 double gs_diff = target_angle - current_angle;
535 // convert desired vertical path angle into a climb rate
536 double des_angle = current_angle - 10 * gs_diff;
538 // estimate horizontal speed towards ILS in meters per minute
539 double dist = last_x - x;
543 double new_vel = ( dist / dt );
545 horiz_vel = 0.75 * horiz_vel + 0.25 * new_vel;
546 // double horiz_vel = cur_fdm_state->get_V_ground_speed()
547 // * SG_FEET_TO_METER * 60.0;
548 // double horiz_vel = airspeed_node->getFloatValue()
549 // * SG_FEET_TO_METER * 60.0;
551 nav_gs_rate_of_climb = -sin( des_angle * SGD_DEGREES_TO_RADIANS )
552 * horiz_vel * SG_METER_TO_FEET;
556 // cout << "not picking up vor. :-(" << endl;
559 if ( nav_valid && nav_inrange && nav_serviceable->getBoolValue() ) {
560 // play station ident via audio system if on + ident,
561 // otherwise turn it off
562 if ( power_btn && (bus_power->getDoubleValue() > 1.0)
563 && nav_ident_btn && audio_btn )
565 SGSoundSample *sound;
566 sound = globals->get_soundmgr()->find( nav_fx_name );
567 if ( sound != NULL ) {
568 sound->set_volume( nav_vol_btn );
570 SG_LOG( SG_COCKPIT, SG_ALERT,
571 "Can't find nav-vor-ident sound" );
573 sound = globals->get_soundmgr()->find( dme_fx_name );
574 if ( sound != NULL ) {
575 sound->set_volume( nav_vol_btn );
577 SG_LOG( SG_COCKPIT, SG_ALERT,
578 "Can't find nav-dme-ident sound" );
580 // cout << "nav_last_time = " << nav_last_time << " ";
581 // cout << "cur_time = "
582 // << globals->get_time_params()->get_cur_time();
584 globals->get_time_params()->get_cur_time() - 30 ) {
585 nav_last_time = globals->get_time_params()->get_cur_time();
588 // cout << " nav_play_count = " << nav_play_count << endl;
589 // cout << "playing = "
590 // << globals->get_soundmgr()->is_playing(nav_fx_name)
592 if ( nav_play_count < 4 ) {
594 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) ) {
595 globals->get_soundmgr()->play_once( nav_fx_name );
598 } else if ( nav_play_count < 5 && nav_has_dme ) {
600 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) &&
601 !globals->get_soundmgr()->is_playing(dme_fx_name) ) {
602 globals->get_soundmgr()->play_once( dme_fx_name );
607 globals->get_soundmgr()->stop( nav_fx_name );
608 globals->get_soundmgr()->stop( dme_fx_name );
614 // Update current nav/adf radio stations based on current postition
615 void FGNavRadio::search()
619 _time_before_search_sec = 1.0;
621 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
622 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
623 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
625 FGNavRecord *nav = NULL;
626 FGNavRecord *loc = NULL;
627 FGNavRecord *dme = NULL;
628 FGNavRecord *gs = NULL;
630 ////////////////////////////////////////////////////////////////////////
632 ////////////////////////////////////////////////////////////////////////
634 nav = globals->get_navlist()->findByFreq(nav_freq, lon, lat, elev);
635 dme = globals->get_dmelist()->findByFreq(nav_freq, lon, lat, elev);
637 loc = globals->get_loclist()->findByFreq(nav_freq, lon, lat, elev);
638 gs = globals->get_gslist()->findByFreq(nav_freq, lon, lat, elev);
642 nav_id = loc->get_ident();
643 // cout << "localizer = " << nav_id << endl;
645 if ( last_nav_id != nav_id || last_nav_vor ) {
646 nav_trans_ident = loc->get_trans_ident();
647 nav_target_radial = loc->get_multiuse();
648 while ( nav_target_radial < 0.0 ) { nav_target_radial += 360.0; }
649 while ( nav_target_radial > 360.0 ) { nav_target_radial -= 360.0; }
650 nav_loclon = loc->get_lon();
651 nav_loclat = loc->get_lat();
652 nav_x = loc->get_x();
653 nav_y = loc->get_y();
654 nav_z = loc->get_z();
655 last_nav_id = nav_id;
656 last_nav_vor = false;
658 nav_has_dme = (dme != NULL);
659 nav_has_gs = (gs != NULL);
661 nav_gslon = gs->get_lon();
662 nav_gslat = gs->get_lat();
663 nav_elev = gs->get_elev_ft();
664 int tmp = (int)(gs->get_multiuse() / 1000.0);
665 nav_target_gs = (double)tmp / 100.0;
666 nav_gs_x = gs->get_x();
667 nav_gs_y = gs->get_y();
668 nav_gs_z = gs->get_z();
670 // derive GS baseline (perpendicular to the runay
672 double tlon, tlat, taz;
673 geo_direct_wgs_84 ( 0.0, nav_gslat, nav_gslon,
674 nav_target_radial + 90,
675 100.0, &tlat, &tlon, &taz );
676 // cout << "nav_target_radial = " << nav_target_radial << endl;
677 // cout << "nav_loc = " << nav_loc << endl;
678 // cout << nav_gslon << "," << nav_gslat << " "
679 // << tlon << "," << tlat << " (" << nav_elev << ")"
681 Point3D p1 = sgGeodToCart( Point3D(tlon*SGD_DEGREES_TO_RADIANS,
682 tlat*SGD_DEGREES_TO_RADIANS,
683 nav_elev*SG_FEET_TO_METER)
685 // cout << nav_gs_x << "," << nav_gs_y << "," << nav_gs_z
687 // cout << p1 << endl;
688 sgdSetVec3( gs_base_vec,
689 p1.x()-nav_gs_x, p1.y()-nav_gs_y, p1.z()-nav_gs_z );
690 // cout << gs_base_vec[0] << "," << gs_base_vec[1] << ","
691 // << gs_base_vec[2] << endl;
693 nav_elev = loc->get_elev_ft();
696 nav_range = FG_LOC_DEFAULT_RANGE;
697 nav_effective_range = nav_range;
699 if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
700 globals->get_soundmgr()->remove( nav_fx_name );
702 SGSoundSample *sound;
703 sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
704 sound->set_volume( 0.3 );
705 globals->get_soundmgr()->add( sound, nav_fx_name );
707 if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
708 globals->get_soundmgr()->remove( dme_fx_name );
710 sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
711 sound->set_volume( 0.3 );
712 globals->get_soundmgr()->add( sound, dme_fx_name );
714 int offset = (int)(sg_random() * 30.0);
715 nav_play_count = offset / 4;
716 nav_last_time = globals->get_time_params()->get_cur_time() -
718 // cout << "offset = " << offset << " play_count = "
720 // << " nav_last_time = " << nav_last_time
721 // << " current time = "
722 // << globals->get_time_params()->get_cur_time() << endl;
724 // cout << "Found an loc station in range" << endl;
725 // cout << " id = " << loc->get_locident() << endl;
727 } else if ( nav != NULL ) {
728 nav_id = nav->get_ident();
729 // cout << "nav = " << nav_id << endl;
731 if ( last_nav_id != nav_id || !last_nav_vor ) {
732 last_nav_id = nav_id;
734 nav_trans_ident = nav->get_trans_ident();
736 nav_has_dme = (dme != NULL);
738 nav_loclon = nav->get_lon();
739 nav_loclat = nav->get_lat();
740 nav_elev = nav->get_elev_ft();
741 nav_twist = nav->get_multiuse();
742 nav_range = nav->get_range();
743 nav_effective_range = adjustNavRange(nav_elev, elev, nav_range);
745 nav_target_radial = nav_sel_radial;
746 nav_x = nav->get_x();
747 nav_y = nav->get_y();
748 nav_z = nav->get_z();
750 if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
751 globals->get_soundmgr()->remove( nav_fx_name );
753 SGSoundSample *sound;
754 sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
755 sound->set_volume( 0.3 );
756 if ( globals->get_soundmgr()->add( sound, nav_fx_name ) ) {
757 // cout << "Added nav-vor-ident sound" << endl;
759 SG_LOG(SG_COCKPIT, SG_WARN, "Failed to add v1-vor-ident sound");
762 if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
763 globals->get_soundmgr()->remove( dme_fx_name );
765 sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
766 sound->set_volume( 0.3 );
767 globals->get_soundmgr()->add( sound, dme_fx_name );
769 int offset = (int)(sg_random() * 30.0);
770 nav_play_count = offset / 4;
771 nav_last_time = globals->get_time_params()->get_cur_time() -
773 // cout << "offset = " << offset << " play_count = "
774 // << nav_play_count << " nav_last_time = "
775 // << nav_last_time << " current time = "
776 // << globals->get_time_params()->get_cur_time() << endl;
778 // cout << "Found a vor station in range" << endl;
779 // cout << " id = " << nav->get_ident() << endl;
784 nav_target_radial = 0;
785 nav_trans_ident = "";
787 if ( ! globals->get_soundmgr()->remove( nav_fx_name ) ) {
788 SG_LOG(SG_COCKPIT, SG_WARN, "Failed to remove nav-vor-ident sound");
790 globals->get_soundmgr()->remove( dme_fx_name );
791 // cout << "not picking up vor1. :-(" << endl;
796 // return the amount of heading needle deflection, returns a value
797 // clamped to the range of ( -10 , 10 )
798 double FGNavRadio::get_nav_cdi_deflection() const {
801 if ( nav_inrange && nav_serviceable->getBoolValue()
802 && cdi_serviceable->getBoolValue() && !nav_slaved_to_gps->getBoolValue() )
804 r = nav_radial - nav_target_radial;
805 // cout << "Target radial = " << nav_target_radial
806 // << " Actual radial = " << nav_radial << endl;
808 while ( r > 180.0 ) { r -= 360.0;}
809 while ( r < -180.0 ) { r += 360.0;}
810 if ( fabs(r) > 90.0 )
811 r = ( r<0.0 ? -r-180.0 : -r+180.0 );
813 // According to Robin Peel, the ILS is 4x more sensitive than a vor
814 r = -r; // reverse, since radial is outbound
815 if ( nav_loc ) { r *= 4.0; }
816 if ( r < -10.0 ) { r = -10.0; }
817 if ( r > 10.0 ) { r = 10.0; }
818 } else if ( nav_slaved_to_gps->getBoolValue() ) {
819 r = gps_cdi_deflection->getDoubleValue();
820 // We want +- 5 dots deflection for the gps, so clamp to -12.5/12.5
821 if ( r < -12.5 ) { r = -12.5; }
822 if ( r > 12.5 ) { r = 12.5; }
830 // return the amount of cross track distance error, returns a meters
831 double FGNavRadio::get_nav_cdi_xtrack_error() const {
835 && nav_serviceable->getBoolValue() && cdi_serviceable->getBoolValue() )
837 r = nav_radial - nav_target_radial;
838 // cout << "Target radial = " << nav_target_radial
839 // << " Actual radial = " << nav_radial
840 // << " r = " << r << endl;
842 while ( r > 180.0 ) { r -= 360.0;}
843 while ( r < -180.0 ) { r += 360.0;}
844 if ( fabs(r) > 90.0 )
845 r = ( r<0.0 ? -r-180.0 : -r+180.0 );
847 r = -r; // reverse, since radial is outbound
849 m = nav_loc_dist * sin(r * SGD_DEGREES_TO_RADIANS);
858 // return the amount of glide slope needle deflection (.i.e. the
859 // number of degrees we are off the glide slope * 5.0
860 double FGNavRadio::get_nav_gs_deflection() const {
861 if ( nav_inrange && nav_has_gs && nav_serviceable->getBoolValue()
862 && gs_serviceable->getBoolValue() && !nav_slaved_to_gps->getBoolValue() )
864 double x = nav_gs_dist;
865 double y = (fgGetDouble("/position/altitude-ft") - nav_elev)
867 // cout << "dist = " << x << " height = " << y << endl;
868 double angle = asin( y / x ) * SGD_RADIANS_TO_DEGREES;
869 return (nav_target_gs - angle) * 5.0;
877 * Return true if the NAV TO flag should be active.
880 FGNavRadio::get_nav_to_flag () const
883 && nav_serviceable->getBoolValue()
884 && tofrom_serviceable->getBoolValue()
885 && !nav_slaved_to_gps->getBoolValue() )
887 double offset = fabs(nav_radial - nav_target_radial);
891 return !(offset <= 90.0 || offset >= 270.0);
893 } else if( nav_slaved_to_gps->getBoolValue() ) {
894 return( gps_to_flag->getBoolValue() );
902 * Return true if the NAV FROM flag should be active.
905 FGNavRadio::get_nav_from_flag () const
908 && nav_serviceable->getBoolValue()
909 && tofrom_serviceable->getBoolValue() ) {
910 double offset = fabs(nav_radial - nav_target_radial);
914 return !(offset > 90.0 && offset < 270.0);
923 * Return the true heading to station
926 FGNavRadio::get_nav_heading () const
933 * Return the current radial.
936 FGNavRadio::get_nav_radial () const
942 FGNavRadio::get_nav_reciprocal_radial () const
944 double recip = nav_radial + 180;
945 if ( recip >= 360 ) {