30#include <simgear/sg_inlines.h>
31#include <simgear/timing/sg_time.hxx>
32#include <simgear/math/sg_random.hxx>
33#include <simgear/misc/sg_path.hxx>
34#include <simgear/math/sg_geodesy.hxx>
35#include <simgear/structure/exception.hxx>
36#include <simgear/math/interpolater.hxx>
37#include <simgear/misc/strutils.hxx>
38#include <simgear/sound/sample_group.hxx>
60 return 4.0 * fabs(xx/4.0 + 0.25 - floor(xx/4.0 + 0.75)) - 1.0;
69static SGVec3d
tangentVector(
const SGGeod& midpoint,
const double heading)
77 SGGeodesy::direct(midpoint, heading, delta, head, az2);
78 SGGeodesy::direct(midpoint, 180+heading, delta, tail, az2);
79 head.setElevationM(midpoint.getElevationM());
80 tail.setElevationM(midpoint.getElevationM());
81 SGVec3d head_xyz = SGVec3d::fromGeod(head);
82 SGVec3d tail_xyz = SGVec3d::fromGeod(tail);
85 return (head_xyz - tail_xyz) * (0.5/delta);
92 SGPropertyNode_ptr n =
93 aParent->getChild(aName, 0,
true)->getChild(
"serviceable", 0,
true);
94 simgear::props::Type typ = n->getType();
95 if ((typ == simgear::props::NONE) || (typ == simgear::props::UNSPECIFIED)) {
96 n->setBoolValue(
true);
110 effective_range(0.0),
115 last_xtrack_error(0.0),
117 _localizerWidth(5.0),
118 _time_before_search_sec(-1.0),
119 _gsCart(SGVec3d::zeros()),
120 _gsAxis(SGVec3d::zeros()),
121 _gsVertical(SGVec3d::zeros()),
125 _cdiCrossTrackErrorM(0.0),
126 _gsNeedleDeflection(0.0),
127 _gsNeedleDeflectionNorm(0.0),
137 SGPath path(
globals->get_fg_root() );
139 term.append(
"Navaids/range.term" );
141 low.append(
"Navaids/range.low" );
143 high.append(
"Navaids/range.high" );
158 if (gps_course_node) {
159 gps_course_node->removeChangeListener(
this);
162 if (nav_slaved_to_gps_node) {
163 nav_slaved_to_gps_node->removeChangeListener(
this);
173 SGPropertyNode* node = _radio_node.get();
178 is_valid_node = node->getChild(
"data-is-valid", 0,
true);
179 vol_btn_node = node->getChild(
"volume", 0,
true);
180 ident_btn_node = node->getChild(
"ident", 0,
true);
181 ident_btn_node->setBoolValue(
true );
182 audio_btn_node = node->getChild(
"audio-btn", 0,
true);
183 audio_btn_node->setBoolValue(
true );
184 backcourse_node = node->getChild(
"back-course-btn", 0,
true);
185 backcourse_node->setBoolValue(
false );
187 nav_serviceable_node = node->getChild(
"serviceable", 0,
true);
192 falseCoursesEnabledNode =
193 fgGetNode(
"/sim/realism/false-radio-courses-enabled");
194 if (!falseCoursesEnabledNode) {
195 falseCoursesEnabledNode =
196 fgGetNode(
"/sim/realism/false-radio-courses-enabled",
true);
197 falseCoursesEnabledNode->setBoolValue(
true);
201 SGPropertyNode *subnode = node->getChild(
"frequencies", 0,
true);
202 freq_node = subnode->getChild(
"selected-mhz", 0,
true);
203 alt_freq_node = subnode->getChild(
"standby-mhz", 0,
true);
204 freq_node->addChangeListener(
this);
205 alt_freq_node->addChangeListener(
this);
207 fmt_freq_node = subnode->getChild(
"selected-mhz-fmt", 0,
true);
208 fmt_alt_freq_node = subnode->getChild(
"standby-mhz-fmt", 0,
true);
209 is_loc_freq_node = subnode->getChild(
"is-localizer-frequency", 0,
true );
212 subnode = node->getChild(
"radials", 0,
true);
213 sel_radial_node = subnode->getChild(
"selected-deg", 0,
true);
214 radial_node = subnode->getChild(
"actual-deg", 0,
true);
215 recip_radial_node = subnode->getChild(
"reciprocal-radial-deg", 0,
true);
216 target_radial_true_node = subnode->getChild(
"target-radial-deg", 0,
true);
217 target_auto_hdg_node = subnode->getChild(
"target-auto-hdg-deg", 0,
true);
220 heading_node = node->getChild(
"heading-deg", 0,
true);
221 time_to_intercept = node->getChild(
"time-to-intercept-sec", 0,
true);
222 to_flag_node = node->getChild(
"to-flag", 0,
true);
223 from_flag_node = node->getChild(
"from-flag", 0,
true);
224 inrange_node = node->getChild(
"in-range", 0,
true);
225 signal_quality_norm_node = node->getChild(
"signal-quality-norm", 0,
true);
226 cdi_deflection_node = node->getChild(
"heading-needle-deflection", 0,
true);
227 cdi_deflection_norm_node = node->getChild(
"heading-needle-deflection-norm", 0,
true);
228 cdi_xtrack_error_node = node->getChild(
"crosstrack-error-m", 0,
true);
229 cdi_xtrack_hdg_err_node
230 = node->getChild(
"crosstrack-heading-error-deg", 0,
true);
231 has_gs_node = node->getChild(
"has-gs", 0,
true);
232 loc_node = node->getChild(
"nav-loc", 0,
true);
233 loc_dist_node = node->getChild(
"nav-distance", 0,
true);
234 gs_deflection_node = node->getChild(
"gs-needle-deflection", 0,
true);
235 gs_deflection_deg_node = node->getChild(
"gs-needle-deflection-deg", 0,
true);
236 gs_deflection_norm_node = node->getChild(
"gs-needle-deflection-norm", 0,
true);
237 gs_direct_node = node->getChild(
"gs-direct-deg", 0,
true);
238 gs_rate_of_climb_node = node->getChild(
"gs-rate-of-climb", 0,
true);
239 gs_rate_of_climb_fpm_node = node->getChild(
"gs-rate-of-climb-fpm", 0,
true);
240 gs_dist_node = node->getChild(
"gs-distance", 0,
true);
241 gs_inrange_node = node->getChild(
"gs-in-range", 0,
true);
243 nav_id_node = node->getChild(
"nav-id", 0,
true);
244 id_c1_node = node->getChild(
"nav-id_asc1", 0,
true);
245 id_c2_node = node->getChild(
"nav-id_asc2", 0,
true);
246 id_c3_node = node->getChild(
"nav-id_asc3", 0,
true);
247 id_c4_node = node->getChild(
"nav-id_asc4", 0,
true);
250 nav_slaved_to_gps_node = node->getChild(
"slaved-to-gps", 0,
true);
251 nav_slaved_to_gps_node->addChangeListener(
this);
253 gps_cdi_deflection_node =
fgGetNode(
"/instrumentation/gps/cdi-deflection",
true);
254 gps_to_flag_node =
fgGetNode(
"/instrumentation/gps/to-flag",
true);
255 gps_from_flag_node =
fgGetNode(
"/instrumentation/gps/from-flag",
true);
256 gps_has_gs_node =
fgGetNode(
"/instrumentation/gps/has-gs",
true);
257 gps_course_node =
fgGetNode(
"/instrumentation/gps/desired-course-deg",
true);
258 gps_course_node->addChangeListener(
this);
260 gps_xtrack_error_nm_node =
fgGetNode(
"/instrumentation/gps/wp/wp[1]/course-error-nm",
true);
261 _magvarNode =
fgGetNode(
"/environment/magnetic-variation-deg",
true);
263 std::ostringstream temp;
265 if( NULL == _audioIdent )
272 node->getNode(
"dme-in-range",
true)->alias(
fgGetNode(
"/instrumentation/dme[0]/in-range",
true),
false);
278 _time_before_search_sec = -1.0;
282double FGNavRadio::adjustNavRange(
double stationElev,
double aircraftElev,
283 double nominalRange )
285 if (nominalRange <= 0.0) {
290 const double usability_factor = 1.3;
298 double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
302 if ( nominalRange < 25.0 + SG_EPSILON ) {
305 }
else if ( nominalRange < 50.0 + SG_EPSILON ) {
320double FGNavRadio::adjustILSRange(
double stationElev,
double aircraftElev,
321 double offsetDegrees,
double distance )
353 if( f < 108.0 || f >= 112.00 )
return false;
354 return (((SGMiscd::roundToInt(f * 100.0) % 100)/10) % 2) != 0;
379void FGNavRadio::updateFormattedFrequencies()
384 snprintf(tmp, 16,
"%.2f", freq_node->getDoubleValue());
385 fmt_freq_node->setStringValue(tmp);
386 snprintf(tmp, 16,
"%.2f", alt_freq_node->getDoubleValue());
387 fmt_alt_freq_node->setStringValue(tmp);
391void FGNavRadio::clearOutputs()
393 inrange_node->setBoolValue(
false );
394 signal_quality_norm_node->setDoubleValue( 0.0 );
395 cdi_deflection_node->setDoubleValue( 0.0 );
396 cdi_deflection_norm_node->setDoubleValue( 0.0 );
397 cdi_xtrack_error_node->setDoubleValue( 0.0 );
398 cdi_xtrack_hdg_err_node->setDoubleValue( 0.0 );
399 time_to_intercept->setDoubleValue( 0.0 );
400 heading_node->setDoubleValue(0.0);
401 gs_deflection_node->setDoubleValue( 0.0 );
402 gs_deflection_deg_node->setDoubleValue(0.0);
403 gs_deflection_norm_node->setDoubleValue(0.0);
404 gs_direct_node->setDoubleValue(0.0);
405 gs_inrange_node->setBoolValue(
false );
406 loc_node->setBoolValue(
false );
407 has_gs_node->setBoolValue(
false);
409 to_flag_node->setBoolValue(
false );
410 from_flag_node->setBoolValue(
false );
411 is_valid_node->setBoolValue(
false);
412 nav_id_node->setStringValue(
"");
417void FGNavRadio::updateReceiver(
double dt)
425 _time_before_search_sec -= dt;
426 if ( _time_before_search_sec < 0 ) {
432 loc_dist = dist(aircraft, _navaid->cart());
433 loc_dist_node->setDoubleValue( loc_dist );
436 if (nav_slaved_to_gps_node->getBoolValue()) {
444 _cdiDeflection = 0.0;
445 _cdiCrossTrackErrorM = 0.0;
446 _toFlag = _fromFlag =
false;
447 _gsNeedleDeflection = 0.0;
448 _gsNeedleDeflectionNorm = 0.0;
449 heading_node->setDoubleValue(0.0);
450 inrange_node->setBoolValue(
false);
451 signal_quality_norm_node->setDoubleValue(0.0);
452 gs_dist_node->setDoubleValue( 0.0 );
453 gs_inrange_node->setBoolValue(
false);
457 double nav_elev = _navaid->get_elev_ft();
459 bool is_loc = loc_node->getBoolValue();
460 double signal_quality_norm = signal_quality_norm_node->getDoubleValue();
468 heading_node->setDoubleValue(hdg);
469 double radial = az2 - twist;
470 double recip = radial + 180.0;
471 SG_NORMALIZE_RANGE(recip, 0.0, 360.0);
472 radial_node->setDoubleValue( radial );
473 recip_radial_node->setDoubleValue( recip );
479 target_radial = sel_radial_node->getDoubleValue();
483 double trtrue = target_radial + twist;
484 SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
485 target_radial_true_node->setDoubleValue( trtrue );
491 double offset = radial - target_radial;
492 SG_NORMALIZE_RANGE(offset, -180.0, 180.0);
495 loc_dist * SG_METER_TO_NM );
501 double effective_range_m = effective_range * SG_NM_TO_METER;
508 double last_signal_quality_norm = signal_quality_norm;
510 if ( loc_dist < effective_range_m ) {
511 signal_quality_norm = 1.0;
513 double range_exceed_norm = loc_dist/effective_range_m;
514 signal_quality_norm = 1/(range_exceed_norm*range_exceed_norm);
517 if (_apply_lowpass_filter) {
518 signal_quality_norm =
fgGetLowPass( last_signal_quality_norm,
519 signal_quality_norm, dt );
521 _apply_lowpass_filter =
true;
523 signal_quality_norm_node->setDoubleValue( signal_quality_norm );
524 bool inrange = signal_quality_norm > 0.2;
525 inrange_node->setBoolValue( inrange );
534 double offset = fabs(radial - target_radial);
535 _toFlag = (offset > 90.0 && offset < 270.0);
537 _fromFlag = !_toFlag;
539 _toFlag = _fromFlag =
false;
543 double r = target_radial - radial;
544 SG_NORMALIZE_RANGE(r, -180.0, 180.0);
547 if (falseCoursesEnabledNode->getBoolValue()) {
551 _cdiDeflection = 30.0 *
sawtooth(r / 30.0);
554 if (fabs(r) > 90.0) {
555 _cdiDeflection = r - copysign(180.0, r);
560 _cdiDeflection = -_cdiDeflection;
563 const double VOR_FULL_ARC = 20.0;
564 _cdiDeflection *= VOR_FULL_ARC / _localizerWidth;
566 if (backcourse_node->getBoolValue()) {
567 _cdiDeflection = -_cdiDeflection;
571 if (fabs(r) > 90.0) {
572 r = ( r<0.0 ? -r-180.0 : -r+180.0 );
577 SG_CLAMP_RANGE(_cdiDeflection, -10.0, 10.0 );
578 _cdiDeflection *= signal_quality_norm;
581 _cdiCrossTrackErrorM = loc_dist * sin(r * SGD_DEGREES_TO_RADIANS);
583 updateGlideSlope(dt, aircraft, signal_quality_norm);
586void FGNavRadio::updateGlideSlope(
double dt,
const SGVec3d& aircraft,
double signal_quality_norm)
588 bool gsInRange = (_gs && inrange_node->getBoolValue());
593 gsDist = dist(aircraft, _gsCart);
594 gsInRange = (gsDist < (_gs->get_range() * SG_NM_TO_METER));
597 gs_inrange_node->setBoolValue(gsInRange);
598 gs_dist_node->setDoubleValue( gsDist );
602 _gsNeedleDeflection = 0.0;
603 _gsNeedleDeflectionNorm = 0.0;
607 SGVec3d pos = aircraft - _gsCart;
610 double comp_h = -dot(pos, _gsAxis);
611 double comp_v = dot(pos, _gsVertical);
617 _gsDirect = atan2(comp_v, comp_h) * SGD_RADIANS_TO_DEGREES;
623 if (_gsDirect < -90.0) _gsDirect += 360.0;
625 double deflectionAngle = target_gs - _gsDirect;
627 if (falseCoursesEnabledNode->getBoolValue()) {
638 if (deflectionAngle < 0) {
639 deflectionAngle = 1.5 *
sawtooth(deflectionAngle / 1.5);
647 SG_CLAMP_RANGE(deflectionAngle, -0.7, 0.7);
653 _gsNeedleDeflection = deflectionAngle * 5.0;
654 _gsNeedleDeflection *= signal_quality_norm;
656 _gsNeedleDeflectionNorm = (deflectionAngle / 0.7) * signal_quality_norm;
661 double gs_diff = target_gs - _gsDirect;
663 double des_angle = _gsDirect - 10 * gs_diff;
668 double elapsedDistance = last_x - gsDist;
671 double new_vel = ( elapsedDistance / dt );
672 horiz_vel = 0.99 * horiz_vel + 0.01 * new_vel;
675 gs_rate_of_climb_node
676 ->setDoubleValue( -sin( des_angle * SGD_DEGREES_TO_RADIANS )
677 * horiz_vel * SG_METER_TO_FEET );
678 gs_rate_of_climb_fpm_node
679 ->setDoubleValue( gs_rate_of_climb_node->getDoubleValue() * 60 );
682void FGNavRadio::valueChanged (SGPropertyNode* prop)
684 if (prop == gps_course_node) {
685 if (!nav_slaved_to_gps_node->getBoolValue()) {
690 double v = prop->getDoubleValue();
691 if (v != sel_radial_node->getDoubleValue()) {
692 sel_radial_node->setDoubleValue(v);
694 }
else if (prop == nav_slaved_to_gps_node) {
695 if (prop->getBoolValue()) {
698 sel_radial_node->setDoubleValue(gps_course_node->getDoubleValue());
702 _apply_lowpass_filter =
false;
703 _time_before_search_sec = 0;
704 }
else if (prop == alt_freq_node) {
705 updateFormattedFrequencies();
706 }
else if (prop == freq_node) {
707 updateFormattedFrequencies();
708 _apply_lowpass_filter =
false;
709 _time_before_search_sec = 0.0;
713void FGNavRadio::updateGPSSlaved()
715 has_gs_node->setBoolValue(gps_has_gs_node->getBoolValue());
717 _toFlag = gps_to_flag_node->getBoolValue();
718 _fromFlag = gps_from_flag_node->getBoolValue();
720 bool gpsValid = (_toFlag | _fromFlag);
721 inrange_node->setBoolValue(gpsValid);
723 signal_quality_norm_node->setDoubleValue(0.0);
724 _cdiDeflection = 0.0;
725 _cdiCrossTrackErrorM = 0.0;
726 _gsNeedleDeflection = 0.0;
727 _gsNeedleDeflectionNorm = 0.0;
733 signal_quality_norm_node->setDoubleValue(1.0);
735 _cdiDeflection = gps_cdi_deflection_node->getDoubleValue();
737 SG_CLAMP_RANGE(_cdiDeflection, -10.0, 10.0 );
739 _cdiCrossTrackErrorM = gps_xtrack_error_nm_node->getDoubleValue() * SG_NM_TO_METER;
740 _gsNeedleDeflection = 0.0;
742 double trtrue = gps_course_node->getDoubleValue() + _magvarNode->getDoubleValue();
743 SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
744 target_radial_true_node->setDoubleValue( trtrue );
747void FGNavRadio::updateCDI(
double dt)
749 bool cdi_serviceable = cdi_serviceable_node->getBoolValue();
750 bool inrange = inrange_node->getBoolValue();
752 if (tofrom_serviceable_node->getBoolValue()) {
753 to_flag_node->setBoolValue(_toFlag);
754 from_flag_node->setBoolValue(_fromFlag);
756 to_flag_node->setBoolValue(
false);
757 from_flag_node->setBoolValue(
false);
760 if (!cdi_serviceable) {
761 _cdiDeflection = 0.0;
762 _cdiCrossTrackErrorM = 0.0;
765 cdi_deflection_node->setDoubleValue(_cdiDeflection);
766 cdi_deflection_norm_node->setDoubleValue(_cdiDeflection * 0.1);
767 cdi_xtrack_error_node->setDoubleValue(_cdiCrossTrackErrorM);
772 double hdg_error = 0.0;
773 if ( inrange && cdi_serviceable ) {
774 double vn =
fgGetDouble(
"/velocities/speed-north-fps" );
775 double ve =
fgGetDouble(
"/velocities/speed-east-fps" );
776 double gnd_trk_true = atan2( ve, vn ) * SGD_RADIANS_TO_DEGREES;
777 if ( gnd_trk_true < 0.0 ) { gnd_trk_true += 360.0; }
779 SGPropertyNode *true_hdg
780 =
fgGetNode(
"/orientation/heading-deg",
true);
781 hdg_error = gnd_trk_true - true_hdg->getDoubleValue();
786 cdi_xtrack_hdg_err_node->setDoubleValue( hdg_error );
800 double adjustment = _cdiDeflection * 3.0;
801 SG_CLAMP_RANGE( adjustment, -30.0, 30.0 );
807 double trtrue = target_radial_true_node->getDoubleValue();
808 if ( loc_node->getBoolValue() && backcourse_node->getBoolValue() ) {
811 SG_NORMALIZE_RANGE(trtrue, 0.0, 360.0);
812 nta_hdg = trtrue - adjustment - hdg_error;
814 nta_hdg = trtrue + adjustment - hdg_error;
817 SG_NORMALIZE_RANGE(nta_hdg, 0.0, 360.0);
818 target_auto_hdg_node->setDoubleValue( nta_hdg );
825 if ( inrange && cdi_serviceable ) {
826 double cur_rate = (last_xtrack_error - _cdiCrossTrackErrorM) / dt;
827 xrate_ms = 0.99 * xrate_ms + 0.01 * cur_rate;
828 if ( fabs(xrate_ms) > 0.00001 ) {
829 t = _cdiCrossTrackErrorM / xrate_ms;
834 time_to_intercept->setDoubleValue( t );
836 if (!gs_serviceable_node->getBoolValue() ) {
837 _gsNeedleDeflection = 0.0;
838 _gsNeedleDeflectionNorm = 0.0;
840 gs_deflection_node->setDoubleValue(_gsNeedleDeflection);
841 gs_deflection_deg_node->setDoubleValue(_gsNeedleDeflectionNorm * 0.7);
842 gs_deflection_norm_node->setDoubleValue(_gsNeedleDeflectionNorm);
843 gs_direct_node->setDoubleValue(_gsDirect);
845 last_xtrack_error = _cdiCrossTrackErrorM;
848void FGNavRadio::updateAudio(
double dt )
850 if (!_navaid || !inrange_node->getBoolValue() || !nav_serviceable_node->getBoolValue()) {
851 _audioIdent->setIdent(
"", 0.0 );
858 || !ident_btn_node->getBoolValue()
859 || !audio_btn_node->getBoolValue() ) {
860 _audioIdent->setIdent(
"", 0.0 );
864 _audioIdent->setIdent( _navaid->get_trans_ident(), vol_btn_node->getFloatValue() );
866 _audioIdent->update( dt );
869FGNavRecord* FGNavRadio::findPrimaryNavaid(
const SGGeod& aPos,
double aFreqMHz)
878 _time_before_search_sec = 1.0;
880 double freq = freq_node->getDoubleValue();
883 _nav_search |= (_last_freq != freq);
890 if (nav == _navaid) {
905 if ((!_nav_search) && (gs == _gs))
925 string identBuffer(4,
' ');
927 nav_id_node->setStringValue(nav->
get_ident());
928 identBuffer = simgear::strutils::rpad( nav->
ident(), 4,
' ' );
935 target_radial = sel_radial_node->getDoubleValue();
943 SG_NORMALIZE_RANGE(target_radial, 0.0, 360.0);
946 target_gs = _gs->glideSlopeAngleDeg();
948 double gs_radial = fmod(_gs->get_multiuse(), 1000.0);
949 SG_NORMALIZE_RANGE(gs_radial, 0.0, 360.0);
950 _gsCart = _gs->cart();
959 _gsVertical = cross(_gsBaseline, _gsAxis);
965 nav_id_node->setStringValue(
"");
966 loc_node->setBoolValue(
false);
967 _audioIdent->setIdent(
"", 0.0 );
970 has_gs_node->setBoolValue(_gs != NULL);
971 is_valid_node->setBoolValue(nav != NULL);
972 id_c1_node->setIntValue( (
int)identBuffer[0] );
973 id_c2_node->setIntValue( (
int)identBuffer[1] );
974 id_c3_node->setIntValue( (
int)identBuffer[2] );
975 id_c4_node->setIntValue( (
int)identBuffer[3] );
981SGSubsystemMgr::InstancedRegistrant<FGNavRadio> registrantFGNavRadio(
983 {{
"instrumentation", SGSubsystemMgr::Dependency::HARD}});
void initServicePowerProperties(SGPropertyNode *node)
void readConfig(SGPropertyNode *config, std::string defaultName)
std::string nodePath() const
void setDefaultPowerSupplyPath(const std::string &p)
specify the default path to use to power the instrument, if it's non- standard.
bool isServiceableAndPowered() const
SGGeod get_aircraft_position() const
static FGNavRecordRef findByFreq(double freq, const SGGeod &position, TypeFilter *filter=nullptr)
Query the database for the specified station.
static TypeFilter * navFilter()
filter matching VOR & ILS/LOC transmitters
FGNavRadio(SGPropertyNode *node)
void update(double dt) override
const char * get_ident() const
double get_elev_ft() const
double get_multiuse() const
double localizerWidth() const
return the localizer width, in degrees computation is based up ICAO stdandard width at the runway thr...
const std::string & ident() const
static std::unique_ptr< SGInterpTable > static_lowRangeInterp
static std::unique_ptr< SGInterpTable > static_terminalRangeInterp
static double sawtooth(double xx)
static std::unique_ptr< SGInterpTable > static_highRangeInterp
static bool IsLocalizerFrequency(double f)
SGPropertyNode_ptr createServiceableProp(SGPropertyNode *aParent, const char *aName)
static SGVec3d tangentVector(const SGGeod &midpoint, const double heading)
const double FG_NAV_DEFAULT_RANGE
const double FG_LOC_DEFAULT_RANGE
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
double fgGetLowPass(double current, double target, double timeratio)
Move a value towards a target.