]> git.mxchange.org Git - flightgear.git/blob - src/ATCDCL/approach.cxx
Merge branch 'jt/runway' into next
[flightgear.git] / src / ATCDCL / approach.cxx
1 // FGApproach - a class to provide approach control at larger airports.
2 //
3 // Written by Alexander Kappes, started March 2002.
4 //
5 // Copyright (C) 2002  Alexander Kappes
6 //
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.
11 //
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.
16 //
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <iostream>
26
27 #include "approach.hxx"
28 #include "transmission.hxx"
29 #include "transmissionlist.hxx"
30 #include "ATCDialog.hxx"
31
32 #include <Airports/runways.hxx>
33 #include <Airports/simple.hxx>
34
35 #include <simgear/constants.h>
36 #include <simgear/math/polar3d.hxx>
37 #include <simgear/misc/sg_path.hxx>
38
39 #include <Environment/environment_mgr.hxx>
40 #include <Environment/environment.hxx>
41
42
43 #include <GUI/gui.h>
44
45 using std::cout;
46 using std::endl;
47
48 //Constructor
49 FGApproach::FGApproach(){
50   comm1_node = fgGetNode("/instrumentation/comm[0]/frequencies/selected-mhz", true);
51   comm2_node = fgGetNode("/instrumentation/comm[1]/frequencies/selected-mhz", true);
52   
53   _type = APPROACH;
54
55   num_planes = 0;
56   lon_node   = fgGetNode("/position/longitude-deg", true);
57   lat_node   = fgGetNode("/position/latitude-deg", true);
58   elev_node  = fgGetNode("/position/altitude-ft", true);
59   hdg_node   = fgGetNode("/orientation/heading-deg", true);
60   speed_node = fgGetNode("/velocities/airspeed-kt", true);
61   etime_node = fgGetNode("/sim/time/elapsed-sec", true);
62
63   first = true;
64   active_runway = "";
65   int i;
66   for ( i=0; i<max_planes; i++) {
67     planes[i].contact   = 0;
68     planes[i].wpn       = 0;
69     planes[i].dnwp      = -999.;
70     planes[i].on_crs    = true;
71     planes[i].turn_rate = 10.0;
72     planes[i].desc_rate = 1000.0;
73     planes[i].clmb_rate = 500.0;
74     planes[i].tlm       = 0.0;
75     planes[i].lmc.c1    = 0;
76     planes[i].lmc.c2    = 0;
77     planes[i].lmc.c3    = -1;
78     planes[i].wp_change = false;
79   }
80 }
81
82 //Destructor
83 FGApproach::~FGApproach(){
84 }
85
86 void FGApproach::Init() {
87 }
88
89
90
91 // ============================================================================
92 // the main update function
93 // ============================================================================
94 void FGApproach::Update(double dt) {
95         
96         const int max_trans = 20;
97         FGTransmission tmissions[max_trans];
98         int    wpn;
99         atc_type station = APPROACH;
100         TransCode code;
101         TransPar TPar;
102         int    i,j;
103         //double course, d, 
104         double adif, datp;
105         //char   buf[10];
106         string message;
107         //static string atcmsg1[10];
108         //static string atcmsg2[10];
109         string mentry;
110         string transm;
111         TransPar tpars;
112         //static bool TransDisplayed = false;
113         
114         update_plane_dat();
115         if ( active_runway == "" ) get_active_runway();
116         
117         double comm1_freq = comm1_node->getDoubleValue();
118         
119         //bool DisplayTransmissions = true;
120         
121         for (i=0; i<num_planes; i++) {
122                 if ( planes[i].ident == "Player") { 
123                         station = APPROACH;
124                         tpars.station = name;
125                         tpars.callsign = "Player";
126                         tpars.airport = ident;
127                         
128                         //cout << "ident = " << ident << " name = " << name << '\n';
129                         
130                         int num_trans = 0;
131                         // is the frequency of the station tuned in?
132                         if ( freq == (int)(comm1_freq*100.0 + 0.5) ) {
133                                 current_transmissionlist->query_station( station, tmissions, max_trans, num_trans );
134                                 // loop over all transmissions for station
135                                 for ( j=0; j<=num_trans-1; j++ ) {
136                                         code = tmissions[j].get_code();
137                                         //cout << "code is " << code.c1 << "  " << code.c2 << "  " << code.c3 << '\n';
138                                         // select proper transmissions
139                                         if(code.c3 != 2) {    // DCL - hack to prevent request crossing airspace being displayed since this isn't implemented yet.
140                                             if ( ( code.c2 == -1 && planes[i].lmc.c3 == 0 ) || 
141                                                     ( code.c1 == 0  && code.c2 == planes[i].lmc.c2 ) ) {
142                                                     mentry = current_transmissionlist->gen_text(station, code, tpars, false);
143                                                     transm = current_transmissionlist->gen_text(station, code, tpars, true);
144                                                     // is the transmission already registered?
145                                                     if (!current_atcdialog->trans_reg( ident, transm, APPROACH )) {
146                                                             current_atcdialog->add_entry( ident, transm, mentry, APPROACH, 0 );
147                                                     }
148                                             }
149                                         }
150                                 }
151                         }
152                 }
153         }
154         
155         for ( i=0; i<num_planes; i++ ) {
156                 //cout << "TPar.airport = " << TPar.airport << " TPar.station = " << TPar.station << " TPar.callsign = " << TPar.callsign << '\n';
157                 //if ( planes[i].ident == TPar.callsign && name == TPar.airport && TPar.station == "approach" ) {
158                         //if ( TPar.request && TPar.intention == "landing" && ident == TPar.intid) {
159                         if(planes[i].ident == "Player" && fgGetBool("/sim/atc/opt0")) {
160                                 //cout << "Landing requested\n";
161                                 fgSetBool("/sim/atc/opt0", false);
162                                 planes[i].wpn = 0; 
163                                 // ===========================
164                                 // === calculate waypoints ===
165                                 // ===========================
166                                 calc_wp( i );  
167                                 update_param( i );
168                                 wpn = planes[i].wpn-1;
169                                 planes[i].aalt = planes[i].wpts[wpn-1][2];
170                                 planes[i].ahdg = planes[i].wpts[wpn][4];
171                                 
172                                 // generate the message
173                                 code.c1 = 1;
174                                 code.c2 = 1;
175                                 code.c3 = 0;
176                                 adif = angle_diff_deg( planes[i].hdg, planes[i].ahdg );
177                                 tpars.station = name;
178                                 tpars.callsign = "Player";
179                                 if ( adif < 0 ) tpars.tdir = 1;
180                                 else            tpars.tdir = 2;
181                                 tpars.heading = planes[i].ahdg;
182                                 if      (planes[i].alt-planes[i].aalt > 100.0)  tpars.VDir = 1;
183                                 else if (planes[i].alt-planes[i].aalt < -100.0) tpars.VDir = 3;
184                                 else tpars.VDir = 2;
185                                 tpars.alt = planes[i].aalt;
186                                 message = current_transmissionlist->gen_text(station, code, tpars, true );
187                                 //cout << message << '\n';
188                                 set_message(message);
189                                 planes[i].lmc = code;
190                                 planes[i].tlm = etime_node->getDoubleValue();
191                                 planes[i].on_crs = true;
192                                 planes[i].contact = 1;
193                         }
194                 //}
195                 
196                 //if(1) {
197                 if ( planes[i].contact == 1 ) {
198                         // =========================
199                         // === update parameters ===
200                         // =========================
201                         update_param( i );
202                         //cout << planes[i].brg << " " << planes[i].dist << " " << planes[i].wpts[wpn+1][0] 
203                         //<< " " << planes[i].wpts[wpn+1][1] << " " << planes[i].wpts[wpn+1][4] 
204                         //cout << wpn << " distance to current course = " << planes[i].dcc << endl;
205                         //cout << etime_node->getDoubleValue() << endl;
206                         
207                         // =========================
208                         // === reached waypoint? ===
209                         // =========================
210                         wpn = planes[i].wpn-2;
211                         adif = angle_diff_deg( planes[i].hdg, planes[i].wpts[wpn][4] ) 
212                         * SGD_DEGREES_TO_RADIANS;
213                         datp = 2*sin(fabs(adif)/2.0)*sin(fabs(adif)/2.0) *
214                                planes[i].spd/3600. * planes[i].turn_rate + 
215                                planes[i].spd/3600. * 3.0;
216                         //cout << adif/SGD_DEGREES_TO_RADIANS << " " 
217                         //     << datp << " " << planes[i].dnc << " " << planes[i].dcc <<endl;
218                         if ( fabs(planes[i].dnc) < datp ) {
219                         //if ( fabs(planes[i].dnc) < 0.3 && planes[i].dnwp < 1.0 ) {
220                                 //cout << "Reached next waypoint!\n";
221                                 planes[i].wpn -= 1;
222                                 wpn = planes[i].wpn-1;
223                                 planes[i].ahdg = planes[i].wpts[wpn][4];
224                                 planes[i].aalt = planes[i].wpts[wpn-1][2];
225                                 planes[i].wp_change = true;
226                                 
227                                 // generate the message
228                                 adif = angle_diff_deg( planes[i].hdg, planes[i].ahdg );
229                                 tpars.station = name;
230                                 tpars.callsign = "Player";
231                                 if ( adif < 0 ) tpars.tdir = 1;
232                                 else            tpars.tdir = 2;
233                                 tpars.heading = planes[i].ahdg;
234                                 
235                                 if ( wpn-1 != 0) { 
236                                         code.c1 = 1;
237                                         code.c2 = 1;
238                                         code.c3 = 0;
239                                         if      (planes[i].alt-planes[i].aalt > 100.0)  tpars.VDir = 1;
240                                         else if (planes[i].alt-planes[i].aalt < -100.0) tpars.VDir = 3;
241                                         else tpars.VDir = 2;
242                                         tpars.alt = planes[i].aalt;
243                                         message = current_transmissionlist->gen_text(station, code, tpars, true );
244                                         //cout << "Approach transmitting...\n";
245                                         //cout << message << endl;
246                                         set_message(message);
247                                 }
248                                 else {
249                                         code.c1 = 1;
250                                         code.c2 = 3;
251                                         code.c3 = 0;
252                                         tpars.runway = active_runway;
253                                         message = current_transmissionlist->gen_text(station, code, tpars, true);
254                                         //cout << "Approach transmitting 2 ...\n";
255                                         //cout << message << endl;
256                                         set_message(message);
257                                 }
258                                 planes[i].lmc = code;
259                                 planes[i].tlm = etime_node->getDoubleValue();
260                                 planes[i].on_crs = true;
261                                 
262                                 update_param( i );
263                         }
264                         
265                         // =========================
266                         // === come off course ? ===
267                         // =========================
268                         if ( fabs(planes[i].dcc) > 1.0 && 
269                            ( !planes[i].wp_change || etime_node->getDoubleValue() - planes[i].tlm > tbm ) ) {
270                                 //cout << "Off course!\n";
271                                 if ( planes[i].on_crs ) {
272                                         if ( planes[i].dcc < 0) {
273                                                 planes[i].ahdg += 30.0;
274                                         }
275                                         else {
276                                                 planes[i].ahdg -= 30.0;
277                                         }
278                                         if (planes[i].ahdg > 360.0) planes[i].ahdg -= 360.0;
279                                         else if (planes[i].ahdg < 0.0) planes[i].ahdg += 360.0;
280                                 }
281                                 //cout << planes[i].on_crs << " " 
282                                 //     << angle_diff_deg( planes[i].hdg, planes[i].ahdg) << " "
283                                 //     << etime_node->getDoubleValue() << " "
284                                 //     << planes[i].tlm << endl;
285                                 // generate the message
286                                 if ( planes[i].on_crs || 
287                                    ( fabs(angle_diff_deg( planes[i].hdg, planes[i].ahdg )) >  30.0  && 
288                                     etime_node->getDoubleValue() - planes[i].tlm > tbm) ) {
289                                         // generate the message
290                                         code.c1 = 1;
291                                         code.c2 = 4;
292                                         code.c3 = 0;
293                                         adif = angle_diff_deg( planes[i].hdg, planes[i].ahdg );
294                                         tpars.station = name;
295                                         tpars.callsign = "Player";
296                                         tpars.miles   = fabs(planes[i].dcc);
297                                         if ( adif < 0 ) tpars.tdir = 1;
298                                         else            tpars.tdir = 2;
299                                         tpars.heading = planes[i].ahdg;
300                                         message = current_transmissionlist->gen_text(station, code, tpars, true);
301                                         //cout << "Approach transmitting 3 ...\n";
302                                         //cout << message << '\n';
303                                         set_message(message);
304                                         planes[i].lmc = code;
305                                         planes[i].tlm = etime_node->getDoubleValue();
306                                 }
307                                 
308                                 planes[i].on_crs = false;
309                         }
310                         else if ( !planes[i].on_crs ) {
311                                 //cout << "Off course 2!\n";
312                                 wpn = planes[i].wpn-1;
313                                 adif = angle_diff_deg( planes[i].hdg, planes[i].wpts[wpn][4] ) 
314                                        * SGD_DEGREES_TO_RADIANS;
315                                 datp = 2*sin(fabs(adif)/2.0)*sin(fabs(adif)/2.0) *
316                                 planes[i].spd/3600. * planes[i].turn_rate + 
317                                 planes[i].spd/3600. * 3.0;
318                                 if ( fabs(planes[i].dcc) < datp ) { 
319                                         planes[i].ahdg = fabs(planes[i].wpts[wpn][4]);
320                                         
321                                         // generate the message
322                                         code.c1 = 1;
323                                         code.c2 = 2;
324                                         code.c3 = 0;
325                                         tpars.station = name;
326                                         tpars.callsign = "Player";
327                                         if ( adif < 0 ) tpars.tdir = 1;
328                                         else            tpars.tdir = 2;
329                                         tpars.heading = planes[i].ahdg;
330                                         message = current_transmissionlist->gen_text(station, code, tpars, true);
331                                         //cout << "Approach transmitting 4 ...\n";
332                                         //cout << message << '\n';
333                                         set_message(message);
334                                         planes[i].lmc = code;
335                                         planes[i].tlm = etime_node->getDoubleValue();
336                                         
337                                         planes[i].on_crs = true;          
338                                 } 
339                         }
340                         else if ( planes[i].wp_change  ) {
341                                 planes[i].wp_change = false;
342                         }
343                         
344                         // ===================================================================
345                         // === Less than two minutes away from touchdown? -> Contact Tower ===
346                         // ===================================================================
347                         if ( planes[i].wpn == 2 && planes[i].dnwp < planes[i].spd/60.*2.0 ) {
348                                 
349                                 double freq = 121.95;   // Hardwired - FIXME
350                                 // generate message
351                                 code.c1 = 1;
352                                 code.c2 = 5;
353                                 code.c3 = 0;
354                                 tpars.station = name;
355                                 tpars.callsign = "Player";
356                                 tpars.freq    = freq;
357                                 message = current_transmissionlist->gen_text(station, code, tpars, true);
358                                 //cout << "Approach transmitting 5 ...\n";
359                                 //cout << message << '\n';
360                                 set_message(message);
361                                 planes[i].lmc = code;
362                                 planes[i].tlm = etime_node->getDoubleValue();
363                                 
364                                 planes[i].contact = 2;
365                         }
366                 }
367         }
368 }
369
370
371 // ============================================================================
372 // update course parameters
373 // ============================================================================
374 void FGApproach::update_param( const int &i ) {
375   
376   double course, d;
377
378   int wpn = planes[i].wpn-1;            // this is the current waypoint
379
380   planes[i].dcc  = calc_psl_dist(planes[i].brg, planes[i].dist,
381                                  planes[i].wpts[wpn][0], planes[i].wpts[wpn][1],
382                                  planes[i].wpts[wpn][4]);
383   planes[i].dnc  = calc_psl_dist(planes[i].brg, planes[i].dist,
384                                  planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
385                                  planes[i].wpts[wpn-1][4]);
386   calc_hd_course_dist(planes[i].brg, planes[i].dist, 
387                       planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
388                       &course, &d);
389   planes[i].dnwp = d;
390
391 }
392
393 // ============================================================================
394 // smallest difference between two angles in degree
395 // difference is negative if a1 > a2 and positive if a2 > a1
396 // ===========================================================================
397 double FGApproach::angle_diff_deg( const double &a1, const double &a2) {
398   
399   double a3 = a2 - a1;
400   if (a3 < 180.0) a3 += 360.0;
401   if (a3 > 180.0) a3 -= 360.0;
402
403   return a3;
404 }
405
406 // ============================================================================
407 // calculate waypoints
408 // ============================================================================
409 void FGApproach::calc_wp( const int &i ) {
410         
411         int j;
412         double course, d, cd, a1;
413         
414         int wpn = planes[i].wpn;
415         // waypoint 0: Threshold of active runway
416         calc_gc_course_dist(Point3D(lon*SGD_DEGREES_TO_RADIANS, lat*SGD_DEGREES_TO_RADIANS, 0.0),
417                             Point3D(active_rw_lon*SGD_DEGREES_TO_RADIANS,active_rw_lat*SGD_DEGREES_TO_RADIANS, 0.0 ),
418                             &course, &d);
419         double d1 = active_rw_hdg+180.0;
420         if ( d1 > 360.0 ) d1 -=360.0;
421         calc_cd_head_dist(360.0-course*SGD_RADIANS_TO_DEGREES, d/SG_NM_TO_METER, 
422                           d1, active_rw_len/SG_NM_TO_METER/2.0, 
423                           &planes[i].wpts[wpn][0], &planes[i].wpts[wpn][1]);
424         planes[i].wpts[wpn][2] = elev;
425         planes[i].wpts[wpn][4] = 0.0;
426         planes[i].wpts[wpn][5] = 0.0;
427         wpn += 1;
428         
429         // ======================
430         // horizontal navigation
431         // ======================
432         // waypoint 1: point for turning onto final
433         calc_cd_head_dist(planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1], d1, lfl,
434                           &planes[i].wpts[wpn][0], &planes[i].wpts[wpn][1]);
435         calc_hd_course_dist(planes[i].wpts[wpn][0],   planes[i].wpts[wpn][1],
436                             planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
437                             &course, &d);
438         planes[i].wpts[wpn][4] = course;
439         planes[i].wpts[wpn][5] = d;
440         wpn += 1;
441         
442         // calculate course and distance from plane position to waypoint 1
443         calc_hd_course_dist(planes[i].brg, planes[i].dist, planes[i].wpts[1][0], 
444                             planes[i].wpts[1][1], &course, &d);
445         // check if airport is not between plane and waypoint 1 and
446         // DCA to airport on course to waypoint 1 is larger than 10 miles
447         double zero = 0.0;
448         if ( fabs(angle_diff_deg( planes[i].wpts[1][0], planes[i].brg  )) < 90.0 ||
449                 calc_psl_dist( zero, zero, planes[i].brg, planes[i].dist, course ) > 10.0 ) {
450                 // check if turning angle at waypoint 1 would be > max_ta
451                 if ( fabs(angle_diff_deg( planes[i].wpts[1][4], course )) > max_ta ) {
452                         cd = calc_psl_dist(planes[i].brg, planes[i].dist,
453                              planes[i].wpts[1][0], planes[i].wpts[1][1],
454                              planes[i].wpts[1][4]);
455                         a1 = atan2(cd,planes[i].wpts[1][1]);
456                         planes[i].wpts[wpn][0] = planes[i].wpts[1][0] - a1/SGD_DEGREES_TO_RADIANS;
457                         if ( planes[i].wpts[wpn][0] < 0.0)   planes[i].wpts[wpn][0] += 360.0;   
458                         if ( planes[i].wpts[wpn][0] > 360.0) planes[i].wpts[wpn][0] -= 360.0;   
459                         planes[i].wpts[wpn][1] = fabs(cd) / sin(fabs(a1));
460                         calc_hd_course_dist(planes[i].wpts[wpn][0],   planes[i].wpts[wpn][1],
461                                             planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
462                                             &course, &d);
463                         planes[i].wpts[wpn][4] = course;
464                         planes[i].wpts[wpn][5] = d;
465                         wpn += 1;
466                         
467                         calc_hd_course_dist(planes[i].brg, planes[i].dist, planes[i].wpts[wpn-1][0], 
468                                             planes[i].wpts[wpn-1][1], &course, &d);
469                 }
470         } else {
471                 double leg = 10.0;
472                 a1 = atan2(planes[i].wpts[1][1], leg );
473                 
474                 if ( angle_diff_deg(planes[i].brg,planes[i].wpts[1][0]) < 0 ) 
475                         planes[i].wpts[wpn][0] = planes[i].wpts[1][0] + a1/SGD_DEGREES_TO_RADIANS;
476                 else planes[i].wpts[wpn][0] = planes[i].wpts[1][0] - a1/SGD_DEGREES_TO_RADIANS;
477                 
478                 planes[i].wpts[wpn][1] = sqrt( planes[i].wpts[1][1]*planes[i].wpts[1][1] + leg*leg );
479                 calc_hd_course_dist(planes[i].wpts[wpn][0],   planes[i].wpts[wpn][1],
480                                     planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
481                                     &course, &d);
482                 planes[i].wpts[wpn][4] = course;
483                 planes[i].wpts[wpn][5] = d;
484                 wpn += 1;
485                 
486                 calc_hd_course_dist(planes[i].brg, planes[i].dist,
487                                     planes[i].wpts[wpn-1][0], planes[i].wpts[wpn-1][1],
488                                     &course, &d);
489         }
490         
491         planes[i].wpts[wpn][0] = planes[i].brg;
492         planes[i].wpts[wpn][1] = planes[i].dist;
493         planes[i].wpts[wpn][2] = planes[i].alt;
494         planes[i].wpts[wpn][4] = course;
495         planes[i].wpts[wpn][5] = d;
496         wpn += 1;
497         
498         planes[i].wpn = wpn;
499         
500         // Now check if legs are too short or if legs can be shortend
501         // legs must be at least 2 flight minutes long
502         double mdist = planes[i].spd / 60.0 * 2.0;
503         for ( j=2; j<wpn-1; ++j ) {
504                 if ( planes[i].wpts[j][1] < mdist) {
505                 }
506         }
507         
508         // ====================
509         // vertical navigation
510         // ====================
511         double alt = elev+3000.0;
512         planes[i].wpts[1][2] = round_alt( true, alt );
513         for ( j=2; j<wpn-1; ++j ) {
514                 double dalt = planes[i].alt - planes[i].wpts[j-1][2];
515                 if ( dalt > 0 ) {
516                         alt = planes[i].wpts[j-1][2] + 
517                               (planes[i].wpts[j][5] / planes[i].spd) * 60.0 * planes[i].desc_rate;
518                         planes[i].wpts[j][2] = round_alt( false, alt );
519                         if ( planes[i].wpts[j][2] > planes[i].alt ) 
520                                 planes[i].wpts[j][2] = round_alt( false, planes[i].alt );
521                 }
522                 else {
523                         planes[i].wpts[j][2] = planes[i].wpts[1][2];
524                 }
525         }
526         
527         cout << "Plane position: " << planes[i].brg << " " << planes[i].dist << endl;
528         for ( j=0; j<wpn; ++j ) {
529                 cout << "Waypoint " << j << endl;
530                 cout << "------------------" << endl;
531                 cout << planes[i].wpts[j][0] << "   " << planes[i].wpts[j][1]
532                      << "   " << planes[i].wpts[j][2] << "   " << planes[i].wpts[j][5]; 
533                 cout << endl << endl;
534         }
535         
536 }
537
538
539 // ============================================================================
540 // round altitude value to next highest/lowest 500 feet
541 // ============================================================================
542 double FGApproach::round_alt( const bool hl, double alt ) {
543
544   alt = alt/1000.0;
545   if ( hl ) {
546     if ( alt > (int)(alt)+0.5 ) alt = ((int)(alt)+1)*1000.0;
547     else alt = ((int)(alt)+0.5)*1000.0;
548   }
549   else {
550     if ( alt > (int)(alt)+0.5 ) alt = ((int)(alt)+0.5)*1000.0;
551     else alt = ((int)(alt))*1000.0;
552   }
553   
554   return alt;
555 }
556
557
558 // ============================================================================
559 // get active runway
560 // ============================================================================
561 void FGApproach::get_active_runway() {
562         //cout << "Entering FGApproach::get_active_runway()\n";
563
564   const FGAirport* apt = fgFindAirportID(ident);
565   assert(apt);
566   FGRunway* runway = apt->getActiveRunwayForUsage();
567
568   active_runway = runway->ident();
569   active_rw_hdg = runway->headingDeg();
570   active_rw_lon = runway->longitude();
571   active_rw_lat = runway->latitude();
572   active_rw_len = runway->lengthFt();
573 }
574
575 // ========================================================================
576 // update infos about plane
577 // ========================================================================
578 void FGApproach::update_plane_dat() {
579   
580   //cout << "Update Approach " << ident << "   " << num_planes << " registered" << endl;
581   // update plane positions
582   int i;
583   for (i=0; i<num_planes; i++) {
584     planes[i].lon = lon_node->getDoubleValue();
585     planes[i].lat = lat_node->getDoubleValue();
586     planes[i].alt = elev_node->getDoubleValue();
587     planes[i].hdg = hdg_node->getDoubleValue();
588     planes[i].spd = speed_node->getDoubleValue();
589
590     /*Point3D aircraft = sgGeodToCart( Point3D(planes[i].lon*SGD_DEGREES_TO_RADIANS, 
591                                              planes[i].lat*SGD_DEGREES_TO_RADIANS, 
592                                              planes[i].alt*SG_FEET_TO_METER) );*/
593     double course, distance;
594     calc_gc_course_dist(Point3D(lon*SGD_DEGREES_TO_RADIANS, lat*SGD_DEGREES_TO_RADIANS, 0.0),
595                         Point3D(planes[i].lon*SGD_DEGREES_TO_RADIANS,planes[i].lat*SGD_DEGREES_TO_RADIANS, 0.0 ),
596                         &course, &distance);
597     planes[i].dist = distance/SG_NM_TO_METER;
598     planes[i].brg  = 360.0-course*SGD_RADIANS_TO_DEGREES;
599
600     //cout << "Plane Id: " << planes[i].ident << "  Distance to " << ident 
601     // << " is " << planes[i].dist << " miles   " << "Bearing " << planes[i].brg << endl;
602     
603   }   
604 }
605
606 // =======================================================================
607 // Add plane to Approach list
608 // =======================================================================
609 void FGApproach::AddPlane(const string& pid) {
610
611   int i;
612   for ( i=0; i<num_planes; i++) {
613     if ( planes[i].ident == pid) {
614       //cout << "Plane already registered: " << planes[i].ident << ' ' << ident << ' ' << num_planes << endl;
615       return;
616     }
617   }
618   planes[num_planes].ident = pid;
619   ++num_planes;
620   //cout << "Plane added to list: " << ident << " " << num_planes << endl;
621   return;
622 }
623
624 // ================================================================================
625 // closest distance between a point (h1,d1) and a straigt line (h2,d2,h3) in 2 dim.
626 // ================================================================================
627 double FGApproach::calc_psl_dist(const double &h1, const double &d1,
628                                  const double &h2, const double &d2,
629                                  const double &h3)
630 {
631   double a1 = h1 * SGD_DEGREES_TO_RADIANS;
632   double a2 = h2 * SGD_DEGREES_TO_RADIANS;
633   double a3 = h3 * SGD_DEGREES_TO_RADIANS;
634   double x1 = cos(a1) * d1;
635   double y1 = sin(a1) * d1;
636   double x2 = cos(a2) * d2;
637   double y2 = sin(a2) * d2;
638   double x3 = cos(a3);
639   double y3 = sin(a3);
640   
641   // formula: dis = sqrt( (v1-v2)**2 - ((v1-v2)*v3)**2 ); vi = (xi,yi)
642   double val1   = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
643   double val2   = ((x1-x2)*x3 + (y1-y2)*y3) * ((x1-x2)*x3 + (y1-y2)*y3);
644   double dis    = val1 - val2;
645   // now get sign for offset 
646   //cout << x1 << " " << x2 << " " << y1 << " " << y2 << " " 
647   //     << x3 << " " << y3 << " " 
648   //     << val1 << " " << val2 << " " << dis << endl;
649   x3 *= sqrt(val2);
650   y3 *= sqrt(val2);
651   double da = fabs(atan2(y3,x3) - atan2(y1-y2,x1-x2));
652   if ( da > SGD_PI ) da -= SGD_2PI;
653   if ( fabs(da) > SGD_PI_2) {
654     //if ( x3*(x1-x2) < 0.0 && y3*(y1-y2) < 0.0) {
655     x3 *= -1.0;
656     y3 *= -1.0;
657   }
658   //cout << x3 << " " << y3 << endl;
659   double dis1   = x1-x2-x3;
660   double dis2   = y1-y2-y3;
661   dis = sqrt(dis);
662   da = atan2(dis2,dis1);
663   if ( da < 0.0 ) da  += SGD_2PI;
664   if ( da < a3 )  dis *= -1.0;
665   //cout << dis1 << " " << dis2 << " " << da*SGD_RADIANS_TO_DEGREES << " " << h3
666   //     << " " << sqrt(dis1*dis1 + dis2*dis2) << " " << dis << endl;
667   //cout << atan2(dis2,dis1)*SGD_RADIANS_TO_DEGREES << " " << dis << endl;
668
669   return dis;
670 }
671
672
673 // ========================================================================
674 // Calculate new bear/dist given starting bear/dis, and offset radial,
675 // and distance.
676 // ========================================================================
677 void FGApproach::calc_cd_head_dist(const double &h1, const double &d1, 
678                                    const double &course, const double &dist,
679                                    double *h2, double *d2)
680 {
681   double a1 = h1 * SGD_DEGREES_TO_RADIANS;
682   double a2 = course * SGD_DEGREES_TO_RADIANS;
683   double x1 = cos(a1) * d1;
684   double y1 = sin(a1) * d1;
685   double x2 = cos(a2) * dist;
686   double y2 = sin(a2) * dist;
687     
688   *d2 = sqrt((x1+x2)*(x1+x2) + (y1+y2)*(y1+y2));
689   *h2 = atan2( (y1+y2), (x1+x2) ) * SGD_RADIANS_TO_DEGREES;
690   if ( *h2 < 0 ) *h2 = *h2+360;
691 }
692
693
694
695 // ========================================================================
696 // get heading and distance between two points; point1 ---> point2
697 // ========================================================================
698 void FGApproach::calc_hd_course_dist(const double &h1, const double &d1, 
699                                      const double &h2, const double &d2,
700                                      double *course, double *dist)
701 {
702   double a1 = h1 * SGD_DEGREES_TO_RADIANS;
703   double a2 = h2 * SGD_DEGREES_TO_RADIANS;
704   double x1 = cos(a1) * d1;
705   double y1 = sin(a1) * d1;
706   double x2 = cos(a2) * d2;
707   double y2 = sin(a2) * d2;
708            
709   *dist   = sqrt( (y2-y1)*(y2-y1) + (x2-x1)*(x2-x1) );
710   *course = atan2( (y2-y1), (x2-x1) ) * SGD_RADIANS_TO_DEGREES;
711   if ( *course < 0 ) *course = *course+360;
712   //cout << x1 << " " << y1 << " " << x2 << " " << y2 << " " << *dist << " " << *course << endl;
713 }
714
715
716
717 int FGApproach::RemovePlane() {
718
719   // first check if anything has to be done
720   bool rmplane = false;
721   int i;
722
723   for (i=0; i<num_planes; i++) {
724     if (planes[i].dist > range*SG_NM_TO_METER) {
725       rmplane = true;
726       break;
727     }
728   }
729   if (!rmplane) return num_planes;
730
731   // now make a copy of the plane list
732   PlaneApp tmp[max_planes];
733   for (i=0; i<num_planes; i++) {
734     tmp[i] = planes[i];
735   }
736   
737   int np = 0;
738   // now check which planes are still in range
739   for (i=0; i<num_planes; i++) {
740     if (tmp[i].dist <= range*SG_NM_TO_METER) {
741       planes[np] = tmp[i];
742       np += 1;
743     }
744   }
745   num_planes = np;
746
747   return num_planes;
748 }
749
750
751 void FGApproach::set_message(const string &msg)
752 {
753   fgSetString("/sim/messages/approach", msg.c_str());
754 }
755