]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/tacan.cxx
Change default Windows terrasync path
[flightgear.git] / src / Instrumentation / tacan.cxx
1 // tacan.cxx - Tactical Navigation Beacon.
2 // Written by Vivian Meazaa, started 2005.
3 //
4 // This file is in the Public Domain and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #  include <config.h>
8 #endif
9
10 #include <simgear/compiler.h>
11 #include <simgear/math/sg_geodesy.hxx>
12 #include <simgear/math/sg_random.h>
13
14 #include <Main/fg_props.hxx>
15 #include <Navaids/navlist.hxx>
16 #include <vector>
17
18 #include "tacan.hxx"
19
20 using std::vector;
21 using std::string;
22
23 /**
24  * Adjust the range.
25  *
26  * Start by calculating the radar horizon based on the elevation
27  * difference, then clamp to the maximum, then add a fudge for
28  * borderline reception.
29  */
30 static double
31 adjust_range (double transmitter_elevation_ft, double aircraft_altitude_ft,
32               double max_range_nm)
33 {
34     max_range_nm = 150;
35     double delta_elevation_ft =
36         fabs(aircraft_altitude_ft - transmitter_elevation_ft);
37     double range_nm = 1.23 * sqrt(delta_elevation_ft);
38     if (range_nm > max_range_nm)
39         range_nm = max_range_nm;
40     else if (range_nm < 20.0)
41         range_nm = 20.0;
42     double rand = sg_random();
43     return range_nm + (range_nm * rand * rand);
44 }
45
46
47 TACAN::TACAN ( SGPropertyNode *node ) :
48     _name(node->getStringValue("name", "tacan")),
49     _num(node->getIntValue("number", 0)),
50     _new_frequency(false),
51     _channel("0000"),
52     _last_distance_nm(0),
53     _frequency_mhz(-1),
54     _time_before_search_sec(0),
55     _mobile_valid(false),
56     _transmitter_valid(false),
57     _transmitter_pos(SGGeod::fromDeg(0, 0)),
58     _transmitter_range_nm(0),
59     _transmitter_bias(0.0),
60     _listener_active(0)
61 {
62 }
63
64 TACAN::~TACAN ()
65 {
66 }
67
68 void
69 TACAN::init ()
70 {
71     string branch;
72     branch = "/instrumentation/" + _name;
73
74     SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
75
76     _serviceable_node = node->getChild("serviceable", 0, true);
77     _ident_node = node->getChild("ident", 0, true);
78
79     SGPropertyNode *fnode = node->getChild("frequencies", 0, true);
80     _frequency_node = fnode->getChild("selected-mhz", 0, true);
81
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);
87
88     _channel_in0_node->addChangeListener(this);
89     _channel_in1_node->addChangeListener(this);
90     _channel_in2_node->addChangeListener(this);
91     _channel_in3_node->addChangeListener(this);
92     _channel_in4_node->addChangeListener(this, true);
93
94     _in_range_node = node->getChild("in-range", 0, true);
95     _distance_node = node->getChild("indicated-distance-nm", 0, true);
96     _speed_node = node->getChild("indicated-ground-speed-kt", 0, true);
97     _time_node = node->getChild("indicated-time-min", 0, true);
98     _name_node = node->getChild("name", 0, true);
99     _bearing_node = node->getChild("indicated-bearing-true-deg", 0, true);
100
101     SGPropertyNode *dnode = node->getChild("display", 0, true);
102     _x_shift_node = dnode->getChild("x-shift", 0, true);
103     _y_shift_node = dnode->getChild("y-shift", 0, true);
104     _rotation_node = dnode->getChild("rotation", 0, true);
105     _channel_node = dnode->getChild("channel", 0, true);
106
107     SGPropertyNode *cnode = fgGetNode("/ai/models/carrier", _num, false );
108     _carrier_name_node = cnode ? cnode->getChild("name", 0, false) : 0;
109
110     SGPropertyNode *tnode = fgGetNode("/ai/models/aircraft", _num, false);
111     _tanker_callsign_node = tnode ? tnode->getChild("callsign", 0, false) : 0;
112
113     SGPropertyNode *mnode = fgGetNode("/ai/models/multiplayer", _num, false);
114     _mp_callsign_node = mnode ? mnode->getChild("callsign", 0, false) : 0;
115
116     _heading_node = fgGetNode("/orientation/heading-deg", true);
117     _electrical_node = fgGetNode("/systems/electrical/outputs/tacan", true);
118 }
119
120 void
121 TACAN::reinit ()
122 {
123     _time_before_search_sec = 0;
124 }
125
126 void
127 TACAN::update (double delta_time_sec)
128 {
129     // don't do anything when paused
130     if (delta_time_sec == 0) return;
131
132     if (!_serviceable_node->getBoolValue() || !_electrical_node->getBoolValue()) {
133         _last_distance_nm = 0;
134         _in_range_node->setBoolValue(false);
135         _distance_node->setDoubleValue(0);
136         _speed_node->setDoubleValue(0);
137         _time_node->setDoubleValue(0);
138         return;
139     }
140
141   SGGeod pos(globals->get_aircraft_position());
142                                 // On timeout, scan again
143     _time_before_search_sec -= delta_time_sec;
144     if ((_time_before_search_sec < 0 || _new_frequency) && _frequency_mhz >= 0)
145         search(_frequency_mhz, pos);
146
147                                  // Calculate the distance to the transmitter
148
149     //calculate the bearing and range of the mobile from the aircraft
150     double mobile_az2 = 0;
151     double mobile_bearing = 0;
152     double mobile_distance = 0;
153
154     geo_inverse_wgs_84(pos, _mobilePos,
155                        &mobile_bearing, &mobile_az2, &mobile_distance);
156
157
158     //calculate the bearing and range of the station from the aircraft
159     double az2 = 0;
160     double bearing = 0;
161     double distance = 0;
162
163     geo_inverse_wgs_84(pos, _transmitter_pos,
164                        &bearing, &az2, &distance);
165
166
167     //select the nearer
168     if ( mobile_distance <= distance && _mobile_valid) {
169         bearing = mobile_bearing;
170         distance = mobile_distance;
171         _transmitter_pos.setElevationFt(_mobilePos.getElevationFt());
172         _transmitter_range_nm = _mobile_range_nm;
173         _transmitter_bias = _mobile_bias;
174         _transmitter_name = _mobile_name;
175         _name_node->setStringValue(_transmitter_name.c_str());
176         _transmitter_ident = _mobile_ident;
177         _ident_node->setStringValue(_transmitter_ident.c_str());
178         _channel_node->setStringValue(_channel.c_str());
179     }
180
181     //// calculate some values for boresight display
182     double distance_nm = distance * SG_METER_TO_NM;
183
184     //// calculate look left/right to target, without yaw correction
185     // double horiz_offset = bearing - heading;
186     //
187     // if (horiz_offset > 180.0) horiz_offset -= 360.0;
188     // if (horiz_offset < -180.0) horiz_offset += 360.0;
189
190     //// now correct look left/right for yaw
191     // horiz_offset += yaw;
192
193     // use the bearing for a plan position indicator display
194
195     double horiz_offset = bearing;
196
197     // calculate values for radar display
198     double y_shift = distance_nm * cos( horiz_offset * SG_DEGREES_TO_RADIANS);
199     double x_shift = distance_nm * sin( horiz_offset * SG_DEGREES_TO_RADIANS);
200
201     double rotation = 0;
202
203     double range_nm = adjust_range(_transmitter_pos.getElevationFt(),
204                                   pos.getElevationFt(),
205                                    _transmitter_range_nm);
206
207     if (distance_nm <= range_nm) {
208         double speed_kt = (fabs(distance_nm - _last_distance_nm) *
209                            ((1 / delta_time_sec) * 3600.0));
210         _last_distance_nm = distance_nm;
211
212         _in_range_node->setBoolValue(true);
213         double tmp_dist = distance_nm - _transmitter_bias;
214         if ( tmp_dist < 0.0 ) {
215             tmp_dist = 0.0;
216         }
217         _distance_node->setDoubleValue( tmp_dist );
218         _speed_node->setDoubleValue(speed_kt);
219         _time_node->setDoubleValue(speed_kt > 0 ? (distance_nm/speed_kt*60.0) : 0);
220         _bearing_node->setDoubleValue(bearing);
221         _x_shift_node->setDoubleValue(x_shift);
222         _y_shift_node->setDoubleValue(y_shift);
223         _rotation_node->setDoubleValue(rotation);
224     } else {
225         _last_distance_nm = 0;
226         _in_range_node->setBoolValue(false);
227         _distance_node->setDoubleValue(0);
228         _speed_node->setDoubleValue(0);
229         _time_node->setDoubleValue(0);
230         _bearing_node->setDoubleValue(0);
231         _x_shift_node->setDoubleValue(0);
232         _y_shift_node->setDoubleValue(0);
233         _rotation_node->setDoubleValue(0);
234     }
235
236                                 // If we can't find a valid station set everything to zero
237     if (!_transmitter_valid && !_mobile_valid ) {
238         _in_range_node->setBoolValue(false);
239         _distance_node->setDoubleValue(0);
240         _speed_node->setDoubleValue(0);
241         _time_node->setDoubleValue(0);
242         _bearing_node->setDoubleValue(0);
243         _x_shift_node->setDoubleValue(0);
244         _y_shift_node->setDoubleValue(0);
245         _rotation_node->setDoubleValue(0);
246         _transmitter_name = "";
247         _name_node->setStringValue(_transmitter_name.c_str());
248         _transmitter_ident = "";
249         _ident_node->setStringValue(_transmitter_ident.c_str());
250         _channel_node->setStringValue(_channel.c_str());
251         return;
252     }
253 } // end function update
254
255 void
256 TACAN::search (double frequency_mhz,const SGGeod& pos)
257 {
258     int number, i;
259     _mobile_valid = false;
260
261     // reset search time
262     _time_before_search_sec = 1.0;
263
264     //try any carriers first
265     FGNavRecord *mobile_tacan = FGNavList::findByFreq( frequency_mhz, FGNavList::carrierFilter() );
266     bool freq_valid = (mobile_tacan != NULL);
267
268     if ( freq_valid ) {
269
270         string str1( mobile_tacan->name() );
271
272         SGPropertyNode * branch = fgGetNode("ai/models", true);
273         vector<SGPropertyNode_ptr> carrier = branch->getChildren("carrier");
274
275         number = carrier.size();
276
277         for ( i = 0; i < number; ++i ) {
278             string str2 ( carrier[i]->getStringValue("name", ""));
279
280             string::size_type loc1= str1.find( str2, 0 );
281             if ( loc1 != string::npos && str2 != "" ) {
282                 _mobilePos = SGGeod::fromDegFt(
283                              carrier[i]->getDoubleValue("position/longitude-deg"),
284                              carrier[i]->getDoubleValue("position/latitude-deg"),
285                              mobile_tacan->get_elev_ft());
286                 _mobile_range_nm = mobile_tacan->get_range();
287                 _mobile_bias = mobile_tacan->get_multiuse();
288                 _mobile_name = mobile_tacan->name();
289                 _mobile_ident = mobile_tacan->get_trans_ident();
290                 _mobile_valid = true;
291                 break;
292             } else {
293                 _mobile_valid = false;
294             }
295         }
296
297         //try any AI tankers second
298         if ( !_mobile_valid) {
299         SGPropertyNode * branch = fgGetNode("ai/models", true);
300         vector<SGPropertyNode_ptr> tanker = branch->getChildren("tanker");
301
302         number = tanker.size();
303
304         for ( i = 0; i < number; ++i ) {
305             string str4 ( tanker[i]->getStringValue("callsign", ""));
306             string::size_type loc1= str1.find( str4, 0 );
307             if ( loc1 != string::npos && str4 != "" ) {
308                 _mobilePos = SGGeod::fromDegFt(
309                                              tanker[i]->getDoubleValue("position/longitude-deg"),
310                                              tanker[i]->getDoubleValue("position/latitude-deg"),
311                                              tanker[i]->getDoubleValue("position/altitude-ft"));
312
313               
314                 _mobile_range_nm = mobile_tacan->get_range();
315                 _mobile_bias = mobile_tacan->get_multiuse();
316                 _mobile_name = mobile_tacan->name();
317                 _mobile_ident = mobile_tacan->get_trans_ident();
318                 _mobile_valid = true;
319                 break;
320             } else {
321                 _mobile_valid = false;
322             }
323         }
324     }
325
326     //try any mp tankers third, if we haven't found the tanker in the ai aircraft
327
328     if ( !_mobile_valid ) {
329         SGPropertyNode * branch = fgGetNode("ai/models", true);
330         vector<SGPropertyNode_ptr> mp_tanker = branch->getChildren("multiplayer");
331
332         number = mp_tanker.size();
333
334         if ( number > 0 ) {       // don't do this if there are no MP aircraft
335             for ( i = 0; i < number; ++i ) {
336                 string str6 ( mp_tanker[i]->getStringValue("callsign", ""));
337                 string::size_type loc1= str1.find( str6, 0 );
338                 if ( loc1 != string::npos && str6 != "" ) {
339                   _mobilePos = SGGeod::fromDegFt(
340                                                  mp_tanker[i]->getDoubleValue("position/longitude-deg"),
341                                                  mp_tanker[i]->getDoubleValue("position/latitude-deg"),
342                                                  mp_tanker[i]->getDoubleValue("position/altitude-ft"));
343
344                   
345                     _mobile_range_nm = mobile_tacan->get_range();
346                     _mobile_bias = mobile_tacan->get_multiuse();
347                     _mobile_name = mobile_tacan->name();
348                     _mobile_ident = mobile_tacan->get_trans_ident();
349                     _mobile_valid = true;
350                     break;
351                 } else {
352                     _mobile_valid = false;
353                 }
354                 }
355             }
356         }
357     } else {
358         _mobile_valid = false;
359     }
360
361     // try the TACAN/VORTAC list next
362     FGNavRecord *tacan = FGNavList::findByFreq( frequency_mhz, pos, FGNavList::tacanFilter() );
363
364     _transmitter_valid = (tacan != NULL);
365
366     if ( _transmitter_valid ) {
367
368         _transmitter_pos = tacan->geod();
369         _transmitter_range_nm = tacan->get_range();
370         _transmitter_bias = tacan->get_multiuse();
371         _transmitter_name = tacan->name();
372         _name_node->setStringValue(_transmitter_name.c_str());
373         _transmitter_ident = tacan->get_trans_ident();
374         _ident_node->setStringValue(_transmitter_ident.c_str());
375     }
376 }
377
378 double
379 TACAN::searchChannel (const string& channel)
380 {
381     double frequency_khz = 0;
382
383     FGTACANRecord *freq
384         = globals->get_channellist()->findByChannel( channel );
385     bool _freq_valid = (freq != NULL);
386     SG_LOG( SG_INSTR, SG_DEBUG, "freq valid " << _freq_valid );
387     if ( _freq_valid ) {
388         frequency_khz = freq->get_freq();
389         SG_LOG( SG_INSTR, SG_DEBUG, "freq output " << frequency_khz );
390         //check sanity
391         if (frequency_khz >= 9620 && frequency_khz <= 121300)
392             return frequency_khz/100;
393     }
394     return frequency_khz = 0;
395 } // end TACAN::searchChannel
396
397 /*
398  * Listener callback. Maintains channel input properties,
399  * searches new channel frequency, updates _channel and
400  * _frequency and sets boolean _new_frequency appropriately.
401  */
402 void
403 TACAN::valueChanged(SGPropertyNode *prop)
404 {
405     if (_listener_active)
406         return;
407     _listener_active++;
408
409     int index = prop->getIndex();
410     string channel = _channel;
411
412     if (index) {  // channel digit or X/Y input
413         int c;
414         if (isdigit(c = _channel_in1_node->getStringValue()[0]))
415             channel[0] = c;
416         if (isdigit(c = _channel_in2_node->getStringValue()[0]))
417             channel[1] = c;
418         if (isdigit(c = _channel_in3_node->getStringValue()[0]))
419             channel[2] = c;
420         c = _channel_in4_node->getStringValue()[0];
421         if (c == 'X' || c == 'Y')
422             channel[3] = c;
423
424     } else {      // channel number input
425         unsigned int f = prop->getIntValue();
426         if (f >= 1 && f <= 126) {
427             channel[0] = '0' + (f / 100) % 10;
428             channel[1] = '0' + (f / 10) % 10;
429             channel[2] = '0' + f % 10;
430         }
431     }
432
433     if (channel != _channel) {
434         SG_LOG(SG_INSTR, SG_DEBUG, "new channel " << channel);
435
436         // write back result
437         _channel_in0_node->setIntValue((channel[0] - '0') * 100
438                 + (channel[1] - '0') * 10 + (channel[2] - '0'));
439         char s[2] = "0";
440         s[0] = channel[0], _channel_in1_node->setStringValue(s);
441         s[0] = channel[1], _channel_in2_node->setStringValue(s);
442         s[0] = channel[2], _channel_in3_node->setStringValue(s);
443         s[0] = channel[3], _channel_in4_node->setStringValue(s);
444
445         // search channel frequency
446         double freq = searchChannel(channel);
447         if (freq != _frequency_mhz) {
448             SG_LOG(SG_INSTR, SG_DEBUG, "new frequency " << freq);
449             _frequency_node->setDoubleValue(freq);
450             _frequency_mhz = freq;
451             _new_frequency = true;
452         }
453
454         _channel = channel;
455         _time_before_search_sec = 0;
456     }
457
458     _listener_active--;
459 }
460
461 // end of TACAN.cxx