13#include <simgear/compiler.h>
14#include <simgear/math/sg_geodesy.hxx>
15#include <simgear/math/sg_random.hxx>
31 double aircraft_altitude_ft,
38 if( station_elevation_ft > 5 )
39 range_nm += 1.23 * sqrt(station_elevation_ft);
41 if( aircraft_altitude_ft > 5 )
42 range_nm += 1.23 * sqrt(aircraft_altitude_ft);
44 return std::max(20., std::min(range_nm, max_range_nm))
45 * (1 + SGMiscd::pow<2>(0.3 * sg_random()));
50 _name(node->getStringValue(
"name",
"tacan")),
51 _num(node->getIntValue(
"number", 0)),
53 _new_frequency(false),
57 _time_before_search_sec(0),
72 std::string branch =
"/instrumentation/" + _name;
74 SGPropertyNode *node =
fgGetNode(branch, _num,
true );
76 _serviceable_node = node->getChild(
"serviceable", 0,
true);
77 _ident_node = node->getChild(
"ident", 0,
true);
79 SGPropertyNode *fnode = node->getChild(
"frequencies", 0,
true);
80 _frequency_node = fnode->getChild(
"selected-mhz", 0,
true);
82 _channel_in0_node = fnode->getChild(
"selected-channel", 0,
true);
83 _channel_in1_node = fnode->getChild(
"selected-channel", 1,
true);
84 _channel_in2_node = fnode->getChild(
"selected-channel", 2,
true);
85 _channel_in3_node = fnode->getChild(
"selected-channel", 3,
true);
86 _channel_in4_node = fnode->getChild(
"selected-channel", 4,
true);
88 _in_range_node = node->getChild(
"in-range", 0,
true);
89 _distance_node = node->getChild(
"indicated-distance-nm", 0,
true);
90 _speed_node = node->getChild(
"indicated-ground-speed-kt", 0,
true);
91 _time_node = node->getChild(
"indicated-time-min", 0,
true);
92 _name_node = node->getChild(
"name", 0,
true);
93 _bearing_node = node->getChild(
"indicated-bearing-true-deg", 0,
true);
95 SGPropertyNode *dnode = node->getChild(
"display", 0,
true);
96 _x_shift_node = dnode->getChild(
"x-shift", 0,
true);
97 _y_shift_node = dnode->getChild(
"y-shift", 0,
true);
98 _channel_node = dnode->getChild(
"channel", 0,
true);
100 _heading_node =
fgGetNode(
"/orientation/heading-deg",
true);
101 _electrical_node =
fgGetNode(
"/systems/electrical/outputs/tacan",
true);
104 _frequency_node->addChangeListener(
this);
106 _channel_in0_node->addChangeListener(
this);
107 _channel_in1_node->addChangeListener(
this);
108 _channel_in2_node->addChangeListener(
this);
109 _channel_in3_node->addChangeListener(
this);
110 _channel_in4_node->addChangeListener(
this,
true);
118 _time_before_search_sec = 0;
125 if( delta_time_sec == 0 )
128 if( !_serviceable_node->getBoolValue()
129 || !_electrical_node->getBoolValue() )
132 SGGeod pos(
globals->get_aircraft_position());
135 _time_before_search_sec -= delta_time_sec;
136 if ((_time_before_search_sec < 0 || _new_frequency) && _frequency_mhz >= 0)
137 search(_frequency_mhz, pos);
139 if( !_active_station || !_active_station->get_serviceable() )
142 const SGGeod& nav_pos = _active_station->geod();
147 double distance = SGLimitsd::max();
148 if( !SGGeodesy::inverse(pos, nav_pos, bearing, az, distance) )
153 sqrt( SGMiscd::pow<2>(distance)
154 + SGMiscd::pow<2>(pos.getElevationM() - nav_pos.getElevationM()) );
155 double distance_nm = distance * SG_METER_TO_NM;
157 double range_nm =
adjust_range( nav_pos.getElevationFt(),
158 pos.getElevationFt(),
159 _active_station->get_range() );
161 if( distance_nm > range_nm )
174 double horiz_offset = bearing;
177 double y_shift = distance_nm * cos(horiz_offset * SG_DEGREES_TO_RADIANS);
178 double x_shift = distance_nm * sin(horiz_offset * SG_DEGREES_TO_RADIANS);
181 double speed_kt = fabs(distance_nm - _last_distance_nm)
182 * (3600 / delta_time_sec);
183 _speed_node->setDoubleValue(speed_kt);
184 _last_distance_nm = distance_nm;
186 double bias = _active_station->get_multiuse();
187 _distance_node->setDoubleValue( SGMiscd::max(0.0, distance_nm - bias) );
189 _time_node->setDoubleValue(speed_kt > 0 ? (distance_nm/speed_kt*60.0) : 0);
190 _bearing_node->setDoubleValue(bearing);
191 _x_shift_node->setDoubleValue(x_shift);
192 _y_shift_node->setDoubleValue(y_shift);
194 _name_node->setStringValue(_active_station->name());
195 _ident_node->setStringValue(_active_station->ident());
196 _in_range_node->setBoolValue(
true);
198 _was_disabled =
false;
202void TACAN::disabled(
bool force)
204 if( _was_disabled && !force )
207 _last_distance_nm = 0;
209 _in_range_node->setBoolValue(
false);
210 _distance_node->setDoubleValue(0);
211 _speed_node->setDoubleValue(0);
212 _time_node->setDoubleValue(0);
213 _bearing_node->setDoubleValue(0);
214 _x_shift_node->setDoubleValue(0);
215 _y_shift_node->setDoubleValue(0);
216 _name_node->setStringValue(
"");
217 _ident_node->setStringValue(
"");
219 _was_disabled =
true;
223void TACAN::search (
double frequency_mhz,
const SGGeod& pos)
226 _time_before_search_sec = 5;
234 double mobile_dist = (mobile_tacan && mobile_tacan->get_serviceable())
235 ? SGGeodesy::distanceM(pos, mobile_tacan->geod())
242 double tacan_dist = tacan
243 ? SGGeodesy::distanceM(pos, tacan->geod())
248 _active_station = mobile_dist < tacan_dist
254double TACAN::searchChannel(
const std::string& channel)
259 SG_LOG(SG_INSTR, SG_DEBUG,
"Invalid TACAN channel: " << channel);
263 double frequency_khz = freq->
get_freq();
264 if(frequency_khz < 9620 || frequency_khz > 121300)
270 "TACAN frequency out of range: " << channel
271 <<
": " << frequency_khz <<
"kHz"
276 return frequency_khz/100;
285TACAN::valueChanged(SGPropertyNode *prop)
287 if (_listener_active)
291 int index = prop->getIndex();
292 std::string channel = _channel;
294 if (std::string(
"selected-mhz") == prop->getNameString())
297 if (
rec !=
nullptr) {
298 channel =
rec->get_channel();
299 _channel_in1_node->setStringValue(channel.substr(0, 1));
300 _channel_in2_node->setStringValue(channel.substr(1, 1));
301 _channel_in3_node->setStringValue(channel.substr(2, 1));
302 _channel_in4_node->setStringValue(channel.substr(3, 1));
306 SG_LOG(SG_INSTR, SG_WARN,
"Entered TACAN Frequency " << prop->getDoubleValue() <<
" does not map to a known TACAN channel");
313 if (isdigit(c = _channel_in1_node->getStringValue()[0]))
315 if (isdigit(c = _channel_in2_node->getStringValue()[0]))
317 if (isdigit(c = _channel_in3_node->getStringValue()[0]))
319 c = _channel_in4_node->getStringValue()[0];
320 if (c ==
'X' || c ==
'Y')
325 unsigned int f = prop->getIntValue();
326 if (f >= 1 && f <= 126) {
327 channel[0] =
'0' + (f / 100) % 10;
328 channel[1] =
'0' + (f / 10) % 10;
329 channel[2] =
'0' + f % 10;
334 if (channel != _channel) {
335 SG_LOG(SG_INSTR, SG_DEBUG,
"new channel " << channel);
338 _channel_in0_node->setIntValue((channel[0] -
'0') * 100
339 + (channel[1] -
'0') * 10 + (channel[2] -
'0'));
341 s[0] = channel[0], _channel_in1_node->setStringValue(s);
342 s[0] = channel[1], _channel_in2_node->setStringValue(s);
343 s[0] = channel[2], _channel_in3_node->setStringValue(s);
344 s[0] = channel[3], _channel_in4_node->setStringValue(s);
347 double freq = searchChannel(channel);
348 if (freq != _frequency_mhz) {
349 SG_LOG(SG_INSTR, SG_DEBUG,
"new frequency " << freq);
350 _frequency_node->setDoubleValue(freq);
351 _frequency_mhz = freq;
352 _new_frequency =
true;
356 _channel_node->setStringValue(_channel);
357 _time_before_search_sec = 0;
366SGSubsystemMgr::InstancedRegistrant<TACAN> registrantTACAN(
368 {{
"instrumentation", SGSubsystemMgr::Dependency::HARD}},
static double adjust_range(double transmitter_elevation_ft, double aircraft_altitude_ft, double max_range_nm)
Fiddle with the reception range a bit.
FGTACANList * get_channellist() const
static FGNavRecordRef findByFreq(double freq, const SGGeod &position, TypeFilter *filter=nullptr)
Query the database for the specified station.
static TypeFilter * mobileTacanFilter()
static TypeFilter * tacanFilter()
Filter returning TACANs and VORTACs.
FGTACANRecord * findByFrequency(int frequency_kHz)
FGTACANRecord * findByChannel(const std::string &channel)
void update(double delta_time_sec) override
TACAN(SGPropertyNode *node)
SGSharedPtr< FGNavRecord > FGNavRecordRef
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
static double adjust_range(double station_elevation_ft, double aircraft_altitude_ft, double max_range_nm)
Adjust the range.