31#include <simgear/math/interpolater.hxx>
32#include <simgear/sg_inlines.h>
33#include <simgear/props/propertyObject.hxx>
34#include <simgear/misc/strutils.hxx>
35#include <simgear/sound/sample_group.hxx>
46using namespace std::string_literals;
47using simgear::PropertyObject;
54 _cdi( rootNode->getNode(
"heading-needle-deflection", true ) ),
55 _cdiNorm( rootNode->getNode(
"heading-needle-deflection-norm", true ) ),
56 _course( rootNode->getNode(
"radials/selected-deg", true ) ),
57 _toFlag( rootNode->getNode(
"to-flag", true ) ),
58 _fromFlag( rootNode->getNode(
"from-flag", true ) ),
59 _signalQuality( rootNode->getNode(
"signal-quality-norm", true ) ),
60 _hasGS( rootNode->getNode(
"has-gs", true ) ),
61 _gsDeflection(rootNode->getNode(
"gs-needle-deflection", true )),
62 _gsDeflectionDeg(rootNode->getNode(
"gs-needle-deflection-deg", true )),
63 _gsDeflectionNorm(rootNode->getNode(
"gs-needle-deflection-norm", true ))
85 _gsDeflectionNorm = norm;
86 _gsDeflectionDeg = norm * 0.7;
87 _gsDeflection = norm * 3.5;
115 return SGMiscd::normalizePeriodic(0.0, 360.0, _course );
120 _signalQuality = signalQuality;
124 PropertyObject<double> _cdi;
125 PropertyObject<double> _cdiNorm;
126 PropertyObject<double> _course;
127 PropertyObject<double> _toFlag;
128 PropertyObject<double> _fromFlag;
129 PropertyObject<double> _signalQuality;
130 PropertyObject<double> _hasGS;
131 PropertyObject<double> _gsDeflection;
132 PropertyObject<double> _gsDeflectionDeg;
133 PropertyObject<double> _gsDeflectionNorm;
143 virtual void update(
double dt,
const SGGeod & aircraftPosition );
144 virtual void search(
double frequency,
const SGGeod & aircraftPosition );
145 virtual double getRange_nm(
const SGGeod & aircraftPosition );
164 return 4.0 * fabs(xx/4.0 + 0.25 - floor(xx/4.0 + 0.75)) - 1.0;
186 void update(
double dt,
const SGGeod & aircraftPosition );
191 PropertyObject<double> _identVolume;
192 PropertyObject<bool> _identEnabled;
197 std::ostringstream temp;
198 temp <<
name <<
"-ident-" << index;
204 _audioIdent( audioIdent ),
205 _identVolume( rootNode->getNode(
name,true)->getNode(
"ident-volume",true) ),
206 _identEnabled( rootNode->getNode(
name,true)->getNode(
"ident-enabled",true) )
219 _audioIdent->update( dt );
222 _audioIdent->setIdent(
"", 0.0 );
225 _audioIdent->setIdent(
_ident, SGMiscd::clip(_identVolume, 0.0, 1.0) );
239 _ident( rootNode->getNode(
name,true)->getNode(
"ident",true) ),
240 _inRange( rootNode->getNode(
name,true)->getNode(
"in-range",true) ),
241 _range_nm( rootNode->getNode(
_name,true)->getNode(
"range-nm",true) )
243 simgear::props::Type typ =
_serviceable.node()->getType();
244 if ((typ == simgear::props::NONE) || (typ == simgear::props::UNSPECIFIED))
256 if( d <= SGLimitsd::min() )
return 25.0;
264 SG_LOG(SG_INSTR,SG_DEBUG,
"No " <<
_name <<
" available at " << frequency );
268 SG_LOG(SG_INSTR, SG_INFO,
276 if( !
valid() )
return 0.0;
283 if ( distance_nm <= range_nm )
return 1.0;
284 return range_nm*range_nm/(distance_nm*distance_nm);
300 double az1 = 0.0, az2 = 0.0, dist = 0.0;
301 SGGeodesy::inverse(aircraftPosition,
_navRecord->geod(), az1, az2, dist );
314 SGPath path(
globals->get_fg_root() );
315 path.append(
"Navaids" );
322 VOR( SGPropertyNode_ptr rootNode);
324 virtual void update(
double dt,
const SGGeod & aircraftPosition );
326 virtual double getRange_nm(
const SGGeod & aircraftPosition);
333 class ServiceVolume {
340 double adjustRange(
double height_ft,
double nominalRange_nm );
343 SGInterpTable term_tbl;
344 SGInterpTable low_tbl;
345 SGInterpTable high_tbl;
348 PropertyObject<double> _radial;
349 PropertyObject<double> _radialInbound;
353double VOR::ServiceVolume::adjustRange(
double height_ft,
double nominalRange_nm )
355 if (nominalRange_nm < SGLimitsd::min() )
359 const double usability_factor = 1.3;
366 if ( nominalRange_nm < 25.0 + SG_EPSILON ) {
368 return term_tbl.interpolate( height_ft ) * usability_factor;
369 }
else if ( nominalRange_nm < 50.0 + SG_EPSILON ) {
372 return low_tbl.interpolate( height_ft ) * nominalRange_nm / 40.0
377 return high_tbl.interpolate( height_ft ) * nominalRange_nm / 130.0
385 rootNode->getIndex()))),
387 _radial( rootNode->getNode(
_name,true)->getNode(
"radial",true) ),
388 _radialInbound( rootNode->getNode(
_name,true)->getNode(
"radial-inbound",true) )
415 double d = cone_of_confusion_width <= SGLimitsd::min() ? 1 :
435 double error = 0.5*(sin(_totalTime/11.0) + sin(_totalTime/23.0));
446 _radialInbound = SGMiscd::normalizePeriodic(0.0,360.0, 180.0 + _radial);
451 if( !
valid() )
return;
453 double offset = SGMiscd::normalizePeriodic(-180.0,180.0,_radial - navIndicator.
getSelectedCourse());
454 bool to = fabs(offset) >= 90.0;
456 if( to ) offset = -offset + copysign(180.0,offset);
458 navIndicator.
showTo( to );
468 LOC( SGPropertyNode_ptr rootNode );
470 virtual void update(
double dt,
const SGGeod & aircraftPosition );
471 virtual void search(
double frequency,
const SGGeod & aircraftPosition );
473 virtual double getRange_nm(
const SGGeod & aircraftPosition);
480 class ServiceVolume {
483 double adjustRange(
double azimuthAngle_deg,
double elevationAngle_deg );
485 SGInterpTable _azimuthTable;
486 SGInterpTable _elevationTable;
488 PropertyObject<double> _localizerOffset_norm;
489 PropertyObject<double> _localizerOffset_m;
490 PropertyObject<double> _localizerWidth_deg;
493LOC::ServiceVolume::ServiceVolume()
500 _azimuthTable.addEntry( 0.0, 1.0 );
501 _azimuthTable.addEntry( 10.0, 1.0 );
502 _azimuthTable.addEntry( 30.0, 0.75 );
503 _azimuthTable.addEntry( 40.0, 0.50 );
504 _azimuthTable.addEntry( 50.0, 0.20 );
505 _azimuthTable.addEntry( 60.0, 0.10 );
506 _azimuthTable.addEntry( 70.0, 0.20 );
507 _azimuthTable.addEntry( 80.0, 0.10 );
508 _azimuthTable.addEntry( 90.0, 0.05 );
509 _azimuthTable.addEntry( 105.0, 0.10 );
510 _azimuthTable.addEntry( 130.0, 0.05 );
511 _azimuthTable.addEntry( 150.0, 0.30 );
512 _azimuthTable.addEntry( 160.0, 0.40 );
513 _azimuthTable.addEntry( 170.0, 0.50 );
514 _azimuthTable.addEntry( 180.0, 0.50 );
516 _elevationTable.addEntry( 0.0, 0.1 );
517 _elevationTable.addEntry( 1.05, 1.0 );
518 _elevationTable.addEntry( 7.00, 1.0 );
519 _elevationTable.addEntry( 45.0, 0.3 );
520 _elevationTable.addEntry( 90.0, 0.1 );
521 _elevationTable.addEntry( 180.0, 0.01 );
524double LOC::ServiceVolume::adjustRange(
double azimuthAngle_deg,
double elevationAngle_deg )
526 return _azimuthTable.interpolate( fabs(azimuthAngle_deg) ) *
527 _elevationTable.interpolate( fabs(elevationAngle_deg) );
532 rootNode->getIndex()))),
534 _localizerOffset_norm( rootNode->getNode(
_name,true)->getNode(
"offset-norm",true) ),
535 _localizerOffset_m( rootNode->getNode(
_name,true)->getNode(
"offset-m",true) ),
536 _localizerWidth_deg( rootNode->getNode(
_name,true)->getNode(
"width-deg",true) )
549void LOC::search(
double frequency,
const SGGeod & aircraftPosition )
553 _localizerWidth_deg = 0.0;
559 _localizerWidth_deg =
_navRecord->localizerWidth();
578 return _navRecord->get_range() * _serviceVolume.adjustRange( azimuthAngle, elevationAngle );
591 _localizerOffset_norm = 0.0;
592 _localizerOffset_m = 0.0;
599 _localizerOffset_m =
_trackDistance_m * sin(offsetDeg * SGD_DEGREES_TO_RADIANS);
604 offsetDeg = 30.0 *
sawtooth(offsetDeg / 30.0);
607 offsetDeg = SGMiscd::clip( 2.0 * offsetDeg / _localizerWidth_deg, -1.0, 1.0 );
609 _localizerOffset_norm = offsetDeg;
617 navIndicator.
showTo(
true );
626 GS( SGPropertyNode_ptr rootNode);
628 virtual void update(
double dt,
const SGGeod & aircraftPosition );
629 virtual void search(
double frequency,
const SGGeod & aircraftPosition );
632 virtual double getRange_nm(
const SGGeod & aircraftPosition);
636 class ServiceVolume {
639 double adjustRange(
double azimuthAngle_deg,
double elevationAngle_deg );
641 SGInterpTable _azimuthTable;
642 SGInterpTable _elevationTable;
644 static SGVec3d tangentVector(
const SGGeod& midpoint,
const double heading);
646 PropertyObject<double> _targetGlideslope_deg;
647 PropertyObject<double> _glideslopeOffset_norm;
652GS::ServiceVolume::ServiceVolume()
659 _azimuthTable.addEntry( 0.0, 1.0 );
660 _azimuthTable.addEntry( 10.0, 1.0 );
661 _azimuthTable.addEntry( 30.0, 0.75 );
662 _azimuthTable.addEntry( 40.0, 0.50 );
663 _azimuthTable.addEntry( 50.0, 0.20 );
664 _azimuthTable.addEntry( 60.0, 0.10 );
665 _azimuthTable.addEntry( 70.0, 0.20 );
666 _azimuthTable.addEntry( 80.0, 0.10 );
667 _azimuthTable.addEntry( 90.0, 0.05 );
668 _azimuthTable.addEntry( 105.0, 0.10 );
669 _azimuthTable.addEntry( 130.0, 0.05 );
670 _azimuthTable.addEntry( 150.0, 0.30 );
671 _azimuthTable.addEntry( 160.0, 0.40 );
672 _azimuthTable.addEntry( 170.0, 0.50 );
673 _azimuthTable.addEntry( 180.0, 0.50 );
675 _elevationTable.addEntry( 0.0, 0.1 );
676 _elevationTable.addEntry( 1.05, 1.0 );
677 _elevationTable.addEntry( 7.00, 1.0 );
678 _elevationTable.addEntry( 45.0, 0.3 );
679 _elevationTable.addEntry( 90.0, 0.1 );
680 _elevationTable.addEntry( 180.0, 0.01 );
683double GS::ServiceVolume::adjustRange(
double azimuthAngle_deg,
double elevationAngle_deg )
685 return _azimuthTable.interpolate( fabs(azimuthAngle_deg) ) *
686 _elevationTable.interpolate( fabs(elevationAngle_deg) );
691 _targetGlideslope_deg( rootNode->getNode(
_name,true)->getNode(
"slope",true) ),
692 _glideslopeOffset_norm( rootNode->getNode(
_name,true)->getNode(
"offset-norm",true) ),
693 _gsAxis(SGVec3d::zeros()),
694 _gsVertical(SGVec3d::zeros())
712 return _navRecord->get_range() * _serviceVolume.adjustRange( azimuthAngle, elevationAngle );
721SGVec3d GS::tangentVector(
const SGGeod& midpoint,
const double heading)
724 const double delta(100.0);
727 SGGeodesy::direct(midpoint, heading, delta, head, az2);
728 SGGeodesy::direct(midpoint, 180+heading, delta, tail, az2);
729 head.setElevationM(midpoint.getElevationM());
730 tail.setElevationM(midpoint.getElevationM());
731 SGVec3d head_xyz = SGVec3d::fromGeod(head);
732 SGVec3d tail_xyz = SGVec3d::fromGeod(tail);
735 return (head_xyz - tail_xyz) * (0.5/delta);
738void GS::search(
double frequency,
const SGGeod & aircraftPosition )
742 _gsAxis = SGVec3d::zeros();
743 _gsVertical = SGVec3d::zeros();
744 _targetGlideslope_deg = 3.0;
748 double gs_radial = SGMiscd::normalizePeriodic(0.0, 360.0, fmod(
_navRecord->get_multiuse(), 1000.0) );
750 _gsAxis = tangentVector(
_navRecord->geod(), gs_radial);
751 SGVec3d gsBaseline = tangentVector(
_navRecord->geod(), gs_radial + 90.0);
752 _gsVertical = cross(gsBaseline, _gsAxis);
754 int tmp = (int)(
_navRecord->get_multiuse() / 1000.0);
756 _targetGlideslope_deg = SGMiscd::max( 1.0, (
double)tmp / 100.0 );
763 _glideslopeOffset_norm = 0.0;
767 SGVec3d pos = SGVec3d::fromGeod(aircraftPosition) -
_navRecord->cart();
770 double comp_h = -dot(pos, _gsAxis);
771 double comp_v = dot(pos, _gsVertical);
777 double gsDirect = atan2(comp_v, comp_h) * SGD_RADIANS_TO_DEGREES;
783 if (gsDirect < -90.0) gsDirect += 360.0;
785 double offset = _targetGlideslope_deg - gsDirect;
787 offset = _targetGlideslope_deg/2 *
sawtooth(2.0*offset/_targetGlideslope_deg);
788 assert( !SGMisc<double>::isNaN(offset) );
791 _glideslopeOffset_norm = SGMiscd::clip(offset/0.7, -1.0, 1.0);
797 navIndicator.
setGS(
false );
800 navIndicator.
setGS(
true );
801 navIndicator.
setGS( _glideslopeOffset_norm );
813 void init()
override;
814 void update(
double dt)
override;
821 Legacy(
NavRadioImpl * navRadioImpl ) : _navRadioImpl( navRadioImpl ) {}
828 SGPropertyNode_ptr is_valid_node;
829 SGPropertyNode_ptr nav_serviceable_node;
830 SGPropertyNode_ptr nav_id_node;
831 SGPropertyNode_ptr id_c1_node;
832 SGPropertyNode_ptr id_c2_node;
833 SGPropertyNode_ptr id_c3_node;
834 SGPropertyNode_ptr id_c4_node;
837 const static int VOR_COMPONENT = 0;
838 const static int LOC_COMPONENT = 1;
839 const static int GS_COMPONENT = 2;
843 SGPropertyNode_ptr _rootNode;
846 std::vector<NavRadioComponent*> _components;
850 PropertyObject<bool> _cdiDisconnected;
851 PropertyObject<std::string> _navType;
856 _name(node->getStringValue(
"name",
"nav")),
857 _num(node->getIntValue(
"number", 0)),
858 _rootNode(
fgGetNode(
"/instrumentation/"s + _name, _num, true)),
859 _useFrequencyFormatter( _rootNode->getNode(
"frequencies/selected-mhz",true), _rootNode->getNode(
"frequencies/selected-mhz-fmt",true), 0.05, 108.0, 118.0 ),
860 _stbyFrequencyFormatter( _rootNode->getNode(
"frequencies/standby-mhz",true), _rootNode->getNode(
"frequencies/standby-mhz-fmt",true), 0.05, 108.0, 118.0 ),
861 _navIndicator(_rootNode),
864 _cdiDisconnected(_rootNode->getNode(
"cdi-disconnected",true)),
865 _navType(_rootNode->getNode(
"nav-type",true))
871 for(
auto p : _components ) {
878 if( ! _components.empty() )
881 _components.push_back(
new VOR(_rootNode) );
882 _components.push_back(
new LOC(_rootNode) );
883 _components.push_back(
new GS(_rootNode) );
888void NavRadioImpl::search()
894 if( dt < SGLimitsd::min() )
return;
899 position =
globals->get_aircraft_position();
901 catch( std::exception & ) {
906 if( _frequency != _useFrequencyFormatter.getFrequency() ) {
907 _frequency = _useFrequencyFormatter.getFrequency();
911 for(
auto p : _components ) {
912 if( _stationTTL <= 0.0 )
913 p->search( _frequency, position );
914 p->update( dt, position );
916 if( !_cdiDisconnected )
917 p->display( _navIndicator );
920 if( _stationTTL <= 0.0 )
923 if( _components[VOR_COMPONENT]->valid() ) {
925 }
else if( _components[LOC_COMPONENT]->valid() ) {
931 _legacy.update( dt );
934void NavRadioImpl::Legacy::init()
936 is_valid_node = _navRadioImpl->_rootNode->getChild(
"data-is-valid", 0,
true);
937 nav_serviceable_node = _navRadioImpl->_rootNode->getChild(
"serviceable", 0,
true);
939 nav_id_node = _navRadioImpl->_rootNode->getChild(
"nav-id", 0,
true );
940 id_c1_node = _navRadioImpl->_rootNode->getChild(
"nav-id_asc1", 0,
true );
941 id_c2_node = _navRadioImpl->_rootNode->getChild(
"nav-id_asc2", 0,
true );
942 id_c3_node = _navRadioImpl->_rootNode->getChild(
"nav-id_asc3", 0,
true );
943 id_c4_node = _navRadioImpl->_rootNode->getChild(
"nav-id_asc4", 0,
true );
947void NavRadioImpl::Legacy::update(
double dt )
949 is_valid_node->setBoolValue(
950 _navRadioImpl->_components[VOR_COMPONENT]->valid() || _navRadioImpl->_components[LOC_COMPONENT]->valid()
953 std::string ident = _navRadioImpl->_components[VOR_COMPONENT]->getIdent();
955 ident = _navRadioImpl->_components[LOC_COMPONENT]->getIdent();
957 nav_id_node->setStringValue( ident );
959 ident = simgear::strutils::rpad( ident, 4,
' ' );
960 id_c1_node->setIntValue( (
int)ident[0] );
961 id_c2_node->setIntValue( (
int)ident[1] );
962 id_c3_node->setIntValue( (
int)ident[2] );
963 id_c4_node->setIntValue( (
int)ident[3] );
970 if(
fgGetBool(
"/instrumentation/use-new-navradio",
false ) )
979SGSubsystemMgr::InstancedRegistrant<NavRadio> registrantNavRadio(
981 {{
"instrumentation", SGSubsystemMgr::Dependency::HARD}});
static TypeFilter * locFilter()
filter matching ILS/LOC transmitter
static FGNavRecordRef findByFreq(double freq, const SGGeod &position, TypeFilter *filter=nullptr)
Query the database for the specified station.
GS(SGPropertyNode_ptr rootNode)
virtual double getRange_nm(const SGGeod &aircraftPosition)
virtual void display(NavIndicator &navIndicator)
virtual void search(double frequency, const SGGeod &aircraftPosition)
virtual FGNavList::TypeFilter * getNavaidFilter()
virtual void update(double dt, const SGGeod &aircraftPosition)
virtual double computeSignalQuality_norm(const SGGeod &aircraftPosition)
virtual double getRange_nm(const SGGeod &aircraftPosition)
virtual void update(double dt, const SGGeod &aircraftPosition)
virtual void search(double frequency, const SGGeod &aircraftPosition)
virtual FGNavList::TypeFilter * getNavaidFilter()
LOC(SGPropertyNode_ptr rootNode)
virtual void display(NavIndicator &navIndicator)
double getSelectedCourse() const
void setSignalQuality(double signalQuality)
void setCDI(double norm)
set the normalized CDI deflection
void setGS(double norm)
set the normalized GS deflection
void setSelectedCourse(double course)
NavIndicator(SGPropertyNode *rootNode)
NavRadioComponentWithIdent(const std::string &name, SGPropertyNode_ptr rootNode, AudioIdent *audioIdent)
static std::string getIdentString(const std::string &name, int index)
void update(double dt, const SGGeod &aircraftPosition)
virtual ~NavRadioComponentWithIdent()
PropertyObject< double > _heightAboveStation_ft
PropertyObject< double > _trueBearingFrom_deg
PropertyObject< double > _trueBearingTo_deg
PropertyObject< double > _range_nm
PropertyObject< std::string > _ident
virtual double getRange_nm(const SGGeod &aircraftPosition)
virtual bool valid() const
PropertyObject< double > _signalQuality_norm
NavRadioComponent(const std::string &name, SGPropertyNode_ptr rootNode)
SGPropertyNode_ptr _rootNode
static double sawtooth(double xx)
PropertyObject< double > _slantDistance_m
virtual void display(NavIndicator &navIndicator)=0
virtual ~NavRadioComponent()
virtual double computeSignalQuality_norm(const SGGeod &aircraftPosition)
PropertyObject< bool > _inRange
virtual void update(double dt, const SGGeod &aircraftPosition)
virtual void search(double frequency, const SGGeod &aircraftPosition)
virtual const std::string getIdent() const
PropertyObject< double > _trackDistance_m
virtual FGNavList::TypeFilter * getNavaidFilter()=0
PropertyObject< bool > _serviceable
NavRadioImpl(SGPropertyNode_ptr node)
void update(double dt) override
static SGSubsystem * createInstance(SGPropertyNode_ptr rootNode)
virtual double computeSignalQuality_norm(const SGGeod &aircraftPosition)
virtual FGNavList::TypeFilter * getNavaidFilter()
virtual double getRange_nm(const SGGeod &aircraftPosition)
VOR(SGPropertyNode_ptr rootNode)
virtual void update(double dt, const SGGeod &aircraftPosition)
virtual void display(NavIndicator &navIndicator)
static SGPath VORTablePath(const char *name)
const double FG_NAV_DEFAULT_RANGE
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.