]> git.mxchange.org Git - flightgear.git/blob - src/ATC/tower.cxx
Further progress towards interactive ATC control
[flightgear.git] / src / ATC / tower.cxx
1 // FGTower - a class to provide tower control at towered airports.
2 //
3 // Written by David Luff, started March 2002.
4 //
5 // Copyright (C) 2002  David C. Luff - david.luff@nottingham.ac.uk
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., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 #include <Main/globals.hxx>
22 #include <Airports/runways.hxx>
23 #include <simgear/math/sg_geodesy.hxx>
24 #include <simgear/debug/logstream.hxx>
25
26 #include "tower.hxx"
27 #include "ATCdisplay.hxx"
28 #include "ATCmgr.hxx"
29 #include "ATCutils.hxx"
30 #include "ATCDialog.hxx"
31 #include "commlist.hxx"
32 #include "AILocalTraffic.hxx"
33
34 SG_USING_STD(cout);
35
36 // TowerPlaneRec
37
38 TowerPlaneRec::TowerPlaneRec() :
39         clearedToLand(false),
40         clearedToLineUp(false),
41         clearedToTakeOff(false),
42         holdShortReported(false),
43         downwindReported(false),
44         longFinalReported(false),
45         longFinalAcknowledged(false),
46         finalReported(false),
47         finalAcknowledged(false),
48         instructedToGoAround(false),
49         onRwy(false),
50         nextOnRwy(false),
51         vfrArrivalReported(false),
52         vfrArrivalAcknowledged(false),
53         opType(TTT_UNKNOWN),
54         leg(LEG_UNKNOWN),
55         landingType(AIP_LT_UNKNOWN),
56         isUser(false) 
57 {
58         plane.callsign = "UNKNOWN";
59 }
60
61 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
62         clearedToLand(false),
63         clearedToLineUp(false),
64         clearedToTakeOff(false),
65         holdShortReported(false),
66         downwindReported(false),
67         longFinalReported(false),
68         longFinalAcknowledged(false),
69         finalReported(false),
70         finalAcknowledged(false),
71         instructedToGoAround(false),
72         onRwy(false),
73         nextOnRwy(false),
74         vfrArrivalReported(false),
75         vfrArrivalAcknowledged(false),
76         opType(TTT_UNKNOWN),
77         leg(LEG_UNKNOWN),
78         landingType(AIP_LT_UNKNOWN),
79         isUser(false)
80 {
81         plane = p;
82 }
83
84 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
85         clearedToLand(false),
86         clearedToLineUp(false),
87         clearedToTakeOff(false),
88         holdShortReported(false),
89         downwindReported(false),
90         longFinalReported(false),
91         longFinalAcknowledged(false),
92         finalReported(false),
93         finalAcknowledged(false),
94         instructedToGoAround(false),
95         onRwy(false),
96         nextOnRwy(false),
97         vfrArrivalReported(false),
98         vfrArrivalAcknowledged(false),
99         opType(TTT_UNKNOWN),
100         leg(LEG_UNKNOWN),
101         landingType(AIP_LT_UNKNOWN),
102         isUser(false)
103 {
104         plane.callsign = "UNKNOWN";
105         pos = pt;
106 }
107
108 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
109         clearedToLand(false),
110         clearedToLineUp(false),
111         clearedToTakeOff(false),
112         holdShortReported(false),
113         downwindReported(false),
114         longFinalReported(false),
115         longFinalAcknowledged(false),
116         finalReported(false),
117         finalAcknowledged(false),
118         instructedToGoAround(false),
119         onRwy(false),
120         nextOnRwy(false),
121         vfrArrivalReported(false),
122         vfrArrivalAcknowledged(false),
123         opType(TTT_UNKNOWN),
124         leg(LEG_UNKNOWN),
125         landingType(AIP_LT_UNKNOWN),
126         isUser(false)
127 {
128         plane = p;
129         pos = pt;
130 }
131
132
133 // FGTower
134
135 /*******************************************
136                TODO List
137                            
138 Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in.
139
140 Tell AI plane to contact ground when taxiing in.
141
142 Use track instead of heading to determine what leg of the circuit the user is flying.
143
144 Use altitude as well as position to try to determine if the user has left the circuit.
145
146 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and 
147 will break when planes start queueing.
148
149 Implement ReportRunwayVacated
150 *******************************************/
151
152 FGTower::FGTower() {
153         ATCmgr = globals->get_ATC_mgr();
154         
155         // Init the property nodes - TODO - need to make sure we're getting surface winds.
156         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
157         wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
158         
159         update_count = 0;
160         update_count_max = 15;
161         
162         holdListItr = holdList.begin();
163         appListItr = appList.begin();
164         depListItr = depList.begin();
165         rwyListItr = rwyList.begin();
166         circuitListItr = circuitList.begin();
167         trafficListItr = trafficList.begin();
168         
169         freqClear = true;
170         
171         timeSinceLastDeparture = 9999;
172         departed = false;
173         
174         nominal_downwind_leg_pos = 1000.0;
175         nominal_base_leg_pos = -1000.0;
176         // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
177 }
178
179 FGTower::~FGTower() {
180         if(!separateGround) {
181                 delete ground;
182         }
183 }
184
185 void FGTower::Init() {
186     display = false;
187         
188         // Pointers to user's position
189         user_lon_node = fgGetNode("/position/longitude-deg", true);
190         user_lat_node = fgGetNode("/position/latitude-deg", true);
191         user_elev_node = fgGetNode("/position/altitude-ft", true);
192         user_hdg_node = fgGetNode("/orientation/heading-deg", true);
193         
194         // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user.
195         // I'll punt the startup issue for now though!!!
196         rwyOccupied = false;
197         
198         // Setup the ground control at this airport
199         AirportATC a;
200         //cout << "Tower ident = " << ident << '\n';
201         if(ATCmgr->GetAirportATCDetails(ident, &a)) {
202                 if(a.ground_freq) {             // Ground control
203                         ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND);
204                         separateGround = true;
205                         if(ground == NULL) {
206                                 // Something has gone wrong :-(
207                                 SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-(");
208                                 ground = new FGGround(ident);
209                                 separateGround = false;
210                                 ground->Init();
211                                 if(display) {
212                                         ground->SetDisplay();
213                                 } else {
214                                         ground->SetNoDisplay();
215                                 }
216                         }
217                 } else {
218                         // Initialise ground anyway to do the shortest path stuff!
219                         // Note that we're now responsible for updating and deleting this - NOT the ATCMgr.
220                         ground = new FGGround(ident);
221                         separateGround = false;
222                         ground->Init();
223                         if(display) {
224                                 ground->SetDisplay();
225                         } else {
226                                 ground->SetNoDisplay();
227                         }
228                 }
229         } else {
230                 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()");
231                 // Initialise ground anyway to avoid segfault later
232                 ground = new FGGround(ident);
233                 separateGround = false;
234                 ground->Init();
235                 if(display) {
236                         ground->SetDisplay();
237                 } else {
238                         ground->SetNoDisplay();
239                 }
240         }
241         
242         // Get the airport elevation
243         aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER;
244         
245         DoRwyDetails();
246         
247         // FIXME - this currently assumes use of the active rwy by the user.
248         rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
249         if(rwyOccupied) {
250                 // Assume the user is started at the threshold ready to take-off
251                 TowerPlaneRec* t = new TowerPlaneRec;
252                 t->plane.callsign = fgGetString("/sim/user/callsign");
253                 t->plane.type = GA_SINGLE;      // FIXME - hardwired!!
254                 t->opType = TTT_UNKNOWN;        // We don't know if the user wants to do circuits or a departure...
255                 t->landingType = AIP_LT_UNKNOWN;
256                 t->leg = TAKEOFF_ROLL;
257                 t->isUser = true;
258                 t->planePtr = NULL;
259                 t->clearedToTakeOff = true;
260                 rwyList.push_back(t);
261                 departed = false;
262         } else {
263                 // For now assume that this means the user is not at the airport and is in the air.
264                 // TODO FIXME - this will break when user starts on apron, at hold short, etc.
265                 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
266         }
267 }
268
269 void FGTower::Update(double dt) {
270         //cout << "T" << endl;
271         // Each time step, what do we need to do?
272         // We need to go through the list of outstanding requests and acknowedgements
273         // and process at least one of them.
274         // We need to go through the list of planes under our control and check if
275         // any need to be addressed.
276         // We need to check for planes not under our control coming within our 
277         // control area and address if necessary.
278         
279         // TODO - a lot of the below probably doesn't need to be called every frame and should be staggered.
280         
281         // Sort the arriving planes
282         
283         /*
284         if(ident == "KEMT") {
285                 cout << update_count << "\ttL: " << trafficList.size() << "  cL: " << circuitList.size() << "  hL: " << holdList.size() << "  aL: " << appList.size() << '\n';
286         }
287         */
288         //if(ident == "EGNX") cout << display << '\n';
289         
290         if(departed != false) {
291                 timeSinceLastDeparture += dt;
292                 //if(ident == "KEMT") 
293                 //      cout << "  dt = " << dt << "  timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
294         }
295         
296         //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n'; 
297         if(respond) {
298                 if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
299                 Respond();
300                 respond = false;
301                 responseReqd = false;
302         }
303         
304         // Calculate the eta of each plane to the threshold.
305         // For ground traffic this is the fastest they can get there.
306         // For air traffic this is the middle approximation.
307         if(update_count == 1) {
308                 doThresholdETACalc();
309         }
310         
311         // Order the list of traffic as per expected threshold use and flag any conflicts
312         if(update_count == 2) {
313                 //bool conflicts = doThresholdUseOrder();
314                 doThresholdUseOrder();
315         }
316         
317         // sortConficts() !!!
318         
319         if(update_count == 4) {
320                 CheckHoldList(dt);
321         }
322         
323         // Uggh - HACK - why have we got rwyOccupied - wouldn't simply testing rwyList.size() do?
324         if(rwyList.size()) {
325                 rwyOccupied = true;
326         } else {
327                 rwyOccupied = false;
328         }
329         
330         if(update_count == 5 && rwyOccupied) {
331                 CheckRunwayList(dt);
332         }
333                 
334         if(update_count == 6) {
335                 CheckCircuitList(dt);
336         }
337         
338         if(update_count == 7) {
339                 CheckApproachList(dt);
340         }
341         
342         // TODO - do one plane from the departure list and set departed = false when out of consideration
343         
344         //doCommunication();
345         
346         if(!separateGround) {
347                 // The display stuff might have to get more clever than this when not separate 
348                 // since the tower and ground might try communicating simultaneously even though
349                 // they're mean't to be the same contoller/frequency!!
350                 if(display) {
351                         ground->SetDisplay();
352                 } else {
353                         ground->SetNoDisplay();
354                 }
355                 ground->Update(dt);
356         }
357         
358         ++update_count;
359         // How big should ii get - ie how long should the update cycle interval stretch?
360         if(update_count >= update_count_max) {
361                 update_count = 0;
362         }
363         
364         // Call the base class update for the response time handling.
365         FGATC::Update(dt);
366
367         if(ident == "KEMT") {   
368                 // For AI debugging convienience - may be removed
369                 Point3D user_pos;
370                 user_pos.setlon(user_lon_node->getDoubleValue());
371                 user_pos.setlat(user_lat_node->getDoubleValue());
372                 user_pos.setelev(user_elev_node->getDoubleValue());
373                 Point3D user_ortho_pos = ortho.ConvertToLocal(user_pos);
374                 fgSetDouble("/AI/user/ortho-x", user_ortho_pos.x());
375                 fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y());
376                 fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue());
377         }
378         
379         //cout << "Done T" << endl;
380 }
381
382 void FGTower::ReceiveUserCallback(int code) {
383         if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
384                 cout << "User requested departure\n";
385         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
386                 VFRArrivalContact("USER");
387         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
388                 VFRArrivalContact("USER", FULL_STOP);
389         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
390                 VFRArrivalContact("USER", TOUCH_AND_GO);
391         }
392 }
393
394 void FGTower::Respond() {
395         cout << "Entering Respond, responseID = " << responseID << endl;
396         TowerPlaneRec* t = FindPlane(responseID);
397         if(t) {
398                 // This will grow!!!
399                 if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
400                         // Testing - hardwire straight in for now
401                         string trns = t->plane.callsign;
402                         trns += " ";
403                         trns += name;
404                         trns += " tower Report three mile straight in for runway ";
405                         trns += ConvertRwyNumToSpokenString(activeRwy);
406                         if(display) {
407                                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
408                         } else {
409                                 cout << "Not displaying, trns was " << trns << '\n';
410                         }
411                         t->vfrArrivalAcknowledged = true;
412                 } else if(t->downwindReported) {
413                         t->downwindReported = false;
414                         int i = 1;
415                         for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
416                                 if((*twrItr)->plane.callsign == responseID) break;
417                                 ++i;
418                         }                       
419                         string trns = "Number ";
420                         trns += ConvertNumToSpokenDigits(i);
421                         trns += " ";
422                         trns += t->plane.callsign;
423                         if(display) {
424                                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
425                         }
426                         if(t->isUser && t->opType == TTT_UNKNOWN) {
427                                 t->opType = CIRCUIT;
428                         }
429                 } else if(t->holdShortReported) {
430                         if(t->nextOnRwy) {
431                                 if(rwyOccupied) {       // TODO - ought to add a sanity check that it isn't this plane only on the runway (even though it shouldn't be!!)
432                                         // Do nothing for now - consider acknowloging hold short eventually
433                                 } else {
434                                         ClearHoldingPlane(t);
435                                         t->leg = TAKEOFF_ROLL;
436                                         rwyList.push_back(t);
437                                         rwyOccupied = true;
438                                         // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
439                                         // FIXME TODO - FIX THIS!!!
440                                         if(holdList.size()) {
441                                                 if(holdListItr == holdList.end()) {
442                                                         holdListItr = holdList.begin();
443                                                 }
444                                                 holdList.erase(holdListItr);
445                                                 holdListItr = holdList.begin();
446                                         }
447                                 }
448                         } else {
449                                 // Tell him to hold and what position he is.
450                                 // Not currently sure under which circumstances we do or don't bother transmitting this.
451                                 string trns = t->plane.callsign;
452                                 trns += " hold position";
453                                 if(display) {
454                                         globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
455                                 }
456                                 // TODO - add some idea of what traffic is blocking him.
457                         }
458                         t->holdShortReported = false;
459                 } else if(t->finalReported && !(t->finalAcknowledged)) {
460                         bool disp = true;
461                         string trns = t->plane.callsign;
462                         cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
463                         cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
464                         if(t->nextOnRwy && !rwyOccupied) {
465                                 if(t->landingType == FULL_STOP) {
466                                         trns += " cleared to land ";
467                                 } else {
468                                         trns += " cleared for the option ";
469                                 }
470                                 // TODO - add winds
471                                 t->clearedToLand = true;
472                         } else if(t->eta < 20) {
473                                 // Do nothing - we'll be telling it to go around in less than 10 seconds if the
474                                 // runway doesn't clear so no point in calling "continue approach".
475                                 disp = false;
476                         } else {
477                                 trns += " continue approach";
478                                 t->clearedToLand = false;
479                         }
480                         if(display && disp) {
481                                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
482                         }
483                         t->finalAcknowledged = true;
484                 }
485         }
486         freqClear = true;       // FIXME - set this to come true after enough time to render the message
487         //cout << "Done Respond" << endl;
488 }
489
490 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - 
491 // this should be done prior to calling this function.
492 void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
493         //cout << "Entering ClearHoldingPlane..." << endl;
494         // Lets Roll !!!!
495         string trns = t->plane.callsign;
496         //if(departed plane < some threshold in time away) {
497         if(0) {         // FIXME
498         //if(timeSinceLastDeparture <= 60.0 && departed == true) {
499                 trns += " line up";
500                 t->clearedToLineUp = true;
501                 t->planePtr->RegisterTransmission(3);   // cleared to line-up
502         //} else if(arriving plane < some threshold away) {
503         } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) {   // Hack - hardwired time
504                 trns += " cleared immediate take-off";
505                 if(trafficList.size()) {
506                         tower_plane_rec_list_iterator trfcItr = trafficList.begin();
507                         trfcItr++;      // At the moment the holding plane should be first in trafficList.
508                         // Note though that this will break if holding planes aren't put in trafficList in the future.
509                         TowerPlaneRec* trfc = *trfcItr;
510                         trns += "... traffic is";
511                         switch(trfc->plane.type) {
512                         case UNKNOWN:
513                                 break;
514                         case GA_SINGLE:
515                                 trns += " a Cessna";    // TODO - add ability to specify actual plane type somewhere
516                                 break;
517                         case GA_HP_SINGLE:
518                                 trns += " a Piper";
519                                 break;
520                         case GA_TWIN:
521                                 trns += " a King-air";
522                                 break;
523                         case GA_JET:
524                                 trns += " a Learjet";
525                                 break;
526                         case MEDIUM:
527                                 trns += " a Regional";
528                                 break;
529                         case HEAVY:
530                                 trns += " a Heavy";
531                                 break;
532                         case MIL_JET:
533                                 trns += " Military";
534                                 break;
535                         }
536                         //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
537                         if(trfc->opType == STRAIGHT_IN) {
538                                 double miles_out = CalcDistOutMiles(trfc);
539                                 if(miles_out < 2) {
540                                         trns += " on final";
541                                 } else {
542                                         trns += " on ";
543                                         trns += ConvertNumToSpokenDigits((int)miles_out);
544                                         trns += " mile final";
545                                 }
546                         } else if(trfc->opType == CIRCUIT) {
547                                 //cout << "Getting leg of " << trfc->plane.callsign << '\n';
548                                 switch(trfc->leg) {
549                                 case FINAL:
550                                         trns += " on final";
551                                         break;
552                                 case TURN4:
553                                         trns += " turning final";
554                                         break;
555                                 case BASE:
556                                         trns += " on base";
557                                         break;
558                                 case TURN3:
559                                         trns += " turning base";
560                                         break;
561                                 case DOWNWIND:
562                                         trns += " in circuit";  // At the moment the user plane is generally flagged as unknown opType when downwind incase its a downwind departure which means we won't get here.
563                                         break;
564                                 // And to eliminate compiler warnings...
565                                 case TAKEOFF_ROLL: break;
566                                 case CLIMBOUT:     break;
567                                 case TURN1:        break;
568                                 case CROSSWIND:    break;
569                                 case TURN2:        break;
570                                 case LANDING_ROLL: break;
571                                 case LEG_UNKNOWN:  break;
572                                 }
573                         }
574                 } else {
575                         // By definition there should be some arriving traffic if we're cleared for immediate takeoff
576                         SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
577                 }
578                 t->clearedToTakeOff = true;
579                 t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
580                 departed = false;
581                 timeSinceLastDeparture = 0.0;
582         } else {
583         //} else if(timeSinceLastDeparture > 60.0 || departed == false) {       // Hack - test for timeSinceLastDeparture should be in lineup block eventually
584                 trns += " cleared for take-off";
585                 // TODO - add traffic is... ?
586                 t->clearedToTakeOff = true;
587                 t->planePtr->RegisterTransmission(4);   // cleared to take-off
588                 departed = false;
589                 timeSinceLastDeparture = 0.0;
590         }
591         if(display) {
592                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
593         }
594         //cout << "Done ClearHoldingPlane " << endl;
595 }
596
597 // Do one plane from the hold list
598 void FGTower::CheckHoldList(double dt) {
599         //cout << "Entering CheckHoldList..." << endl;
600         if(holdList.size()) {
601                 //cout << "*holdListItr = " << *holdListItr << endl;
602                 if(holdListItr == holdList.end()) {
603                         holdListItr = holdList.begin();
604                 }
605                 //cout << "*holdListItr = " << *holdListItr << endl;
606                 //Process(*holdListItr);
607                 TowerPlaneRec* t = *holdListItr;
608                 //cout << "t = " << t << endl;
609                 if(t->holdShortReported) {
610                         // NO-OP - leave it to the response handler.
611                 } else {        // not responding to report, but still need to clear if clear
612                         if(t->nextOnRwy) {
613                                 //cout << "departed = " << departed << '\n';
614                                 //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
615                                 if(rwyOccupied) {
616                                         // Do nothing
617                                 } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
618                                         // Do nothing - this is a bit of a hack - should maybe do line up be ready here
619                                 } else {
620                                         ClearHoldingPlane(t);
621                                         t->leg = TAKEOFF_ROLL;
622                                         rwyList.push_back(t);
623                                         rwyOccupied = true;
624                                         holdList.erase(holdListItr);
625                                         holdListItr = holdList.begin();
626                                 }
627                         }
628                         // TODO - rationalise the considerable code duplication above!
629                 }
630                 ++holdListItr;
631         }
632         //cout << "Done CheckHoldList" << endl;
633 }
634
635 // do the ciruit list
636 void FGTower::CheckCircuitList(double dt) {
637         //cout << "Entering CheckCircuitList..." << endl;
638         // Clear the constraints - we recalculate here.
639         base_leg_pos = 0.0;
640         downwind_leg_pos = 0.0;
641         crosswind_leg_pos = 0.0;
642         
643         if(circuitList.size()) {        // Do one plane from the circuit
644                 if(circuitListItr == circuitList.end()) {
645                         circuitListItr = circuitList.begin();
646                 }
647                 TowerPlaneRec* t = *circuitListItr;
648                 if(t->isUser) {
649                         t->pos.setlon(user_lon_node->getDoubleValue());
650                         t->pos.setlat(user_lat_node->getDoubleValue());
651                         t->pos.setelev(user_elev_node->getDoubleValue());
652                 } else {
653                         t->pos = t->planePtr->GetPos();         // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this.
654                         t->landingType = t->planePtr->GetLandingOption();
655                         //cout << "AI plane landing option is " << t->landingType << '\n';
656                 }
657                 Point3D tortho = ortho.ConvertToLocal(t->pos);
658                 if(t->isUser) {
659                         // Need to figure out which leg he's on
660                         //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue();
661                         double ho = GetAngleDiff_deg(user_hdg_node->getDoubleValue(), rwy.hdg);
662                         //cout << " ho = " << ho << " abs(ho = " << abs(ho) << '\n';
663                         // TODO FIXME - get the wind and convert this to track, or otherwise use track somehow!!!
664                         // If it's gusty might need to filter the value, although we are leaving 30 degrees each way leeway!
665                         if(abs(ho) < 30) {
666                                 // could be either takeoff, climbout or landing - check orthopos.y
667                                 //cout << "tortho.y = " << tortho.y() << '\n';
668                                 if((tortho.y() < 0) || (t->leg == TURN4) || (t->leg == FINAL)) {
669                                         t->leg = FINAL;
670                                         //cout << "Final\n";
671                                 } else {
672                                         t->leg = CLIMBOUT;      // TODO - check elev wrt. apt elev to differentiate takeoff roll and climbout
673                                         //cout << "Climbout\n";
674                                         // If it's the user we may be unsure of his/her intentions.
675                                         // (Hopefully the AI planes won't try confusing the sim!!!)
676                                         if(t->opType == TTT_UNKNOWN) {
677                                                 if(tortho.y() > 5000) {
678                                                         // 5 km out from threshold - assume it's a departure
679                                                         t->opType = OUTBOUND;   // TODO - could check if the user has climbed significantly above circuit altitude as well.
680                                                         // Since we are unknown operation we should be in depList already.
681                                                         circuitList.erase(circuitListItr);
682                                                         RemoveFromTrafficList(t->plane.callsign);
683                                                         circuitListItr = circuitList.begin();
684                                                 }
685                                         } else if(t->opType == CIRCUIT) {
686                                                 if(tortho.y() > 10000) {
687                                                         // 10 km out - assume the user has abandoned the circuit!!
688                                                         t->opType = OUTBOUND;
689                                                         depList.push_back(t);
690                                                         circuitList.erase(circuitListItr);
691                                                         circuitListItr = circuitList.begin();
692                                                 }
693                                         }
694                                 }
695                         } else if(abs(ho) < 60) {
696                                 // turn1 or turn 4
697                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
698                                 if((t->leg == CLIMBOUT) || (t->leg == TURN1)) {
699                                         t->leg = TURN1;
700                                         //cout << "Turn1\n";
701                                 } else {
702                                         t->leg = TURN4;
703                                         //cout << "Turn4\n";
704                                 }
705                         } else if(abs(ho) < 120) {
706                                 // crosswind or base
707                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
708                                 if((t->leg == TURN1) || (t->leg == CROSSWIND)) {
709                                         t->leg = CROSSWIND;
710                                         //cout << "Crosswind\n";
711                                 } else {
712                                         t->leg = BASE;
713                                         //cout << "Base\n";
714                                 }
715                         } else if(abs(ho) < 150) {
716                                 // turn2 or turn 3
717                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
718                                 if((t->leg == CROSSWIND) || (t->leg == TURN2)) {
719                                         t->leg = TURN2;
720                                         //cout << "Turn2\n";
721                                 } else {
722                                         t->leg = TURN3;
723                                         // Probably safe now to assume the user is flying a circuit
724                                         t->opType = CIRCUIT;
725                                         //cout << "Turn3\n";
726                                 }
727                         } else {
728                                 // downwind
729                                 t->leg = DOWNWIND;
730                                 //cout << "Downwind\n";
731                         }
732                         if(t->leg == FINAL) {
733                                 if(OnActiveRunway(t->pos)) {
734                                         t->leg = LANDING_ROLL;
735                                 }
736                         }
737                 } else {
738                         t->leg = t->planePtr->GetLeg();
739                 }
740                 
741                 // Set the constraints IF this is the first plane in the circuit
742                 // TODO - at the moment we're constraining plane 2 based on plane 1 - this won't (or might not) work for 3 planes in the circuit!!
743                 if(circuitListItr == circuitList.begin()) {
744                         switch(t->leg) {
745                         case FINAL:
746                                 // Base leg must be at least as far out as the plane is - actually possibly not necessary for separation, but we'll use that for now.
747                                 base_leg_pos = tortho.y();
748                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
749                                 break;
750                         case TURN4:
751                                 // Fall through to base
752                         case BASE:
753                                 base_leg_pos = tortho.y();
754                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
755                                 break;
756                         case TURN3:
757                                 // Fall through to downwind
758                         case DOWNWIND:
759                                 // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
760                                 base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
761                                 //cout << "base_leg_pos = " << base_leg_pos;
762                                 downwind_leg_pos = tortho.x();          // Assume that a following plane can simply be constrained by the immediately in front downwind plane
763                                 //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
764                                 break;
765                         case TURN2:
766                                 // Fall through to crosswind
767                         case CROSSWIND:
768                                 crosswind_leg_pos = tortho.y();
769                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
770                                 t->instructedToGoAround = false;
771                                 break;
772                         case TURN1:
773                                 // Fall through to climbout
774                         case CLIMBOUT:
775                                 // Only use current by constraint as largest
776                                 crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
777                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
778                                 break;
779                         case TAKEOFF_ROLL:
780                                 break;
781                         case LEG_UNKNOWN:
782                                 break;
783                         case LANDING_ROLL:
784                                 break;
785                         default:
786                                 break;
787                         }
788                 }
789                 
790                 if(t->leg == FINAL) {
791                         if(t->landingType == FULL_STOP) t->opType = INBOUND;
792                         if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
793                                 // TODO - need to make this more sophisticated 
794                                 // eg. is the plane accelerating down the runway taking off [OK],
795                                 // or stationary near the start [V. BAD!!].
796                                 // For now this should stop the AI plane landing on top of the user.
797                                 string trns = t->plane.callsign;
798                                 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
799                                 if(display) {
800                                         globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
801                                 }
802                                 t->instructedToGoAround = true;
803                                 if(t->planePtr) {
804                                         cout << "Registering Go-around transmission with AI plane\n";
805                                         t->planePtr->RegisterTransmission(13);
806                                 }
807                         }
808                 } else if(t->leg == LANDING_ROLL) {
809                         rwyList.push_front(t);
810                         // TODO - if(!clearedToLand) shout something!!
811                         t->clearedToLand = false;
812                         RemoveFromTrafficList(t->plane.callsign);
813                         if(t->isUser) {
814                                 t->opType = TTT_UNKNOWN;
815                         }       // TODO - allow the user to specify opType via ATC menu
816                         circuitListItr = circuitList.erase(circuitListItr);
817                         if(circuitListItr == circuitList.end() ) {
818                                 circuitListItr = circuitList.begin();
819                         }
820                 }
821                 ++circuitListItr;
822         }
823         //cout << "Done CheckCircuitList" << endl;
824 }
825
826 // Do the runway list - we'll do the whole runway list since it's important and there'll never be many planes on the rwy at once!!
827 // FIXME - at the moment it looks like we're only doing the first plane from the rwy list.
828 // (However, at the moment there should only be one airplane on the rwy at once, until we
829 // start allowing planes to line up whilst previous arrival clears the rwy.)
830 void FGTower::CheckRunwayList(double dt) {
831         //cout << "Entering CheckRunwayList..." << endl;
832         if(rwyOccupied) {
833                 if(!rwyList.size()) {
834                         rwyOccupied = false;
835                 } else {
836                         rwyListItr = rwyList.begin();
837                         TowerPlaneRec* t = *rwyListItr;
838                         if(t->isUser) {
839                                 t->pos.setlon(user_lon_node->getDoubleValue());
840                                 t->pos.setlat(user_lat_node->getDoubleValue());
841                                 t->pos.setelev(user_elev_node->getDoubleValue());
842                         } else {
843                                 t->pos = t->planePtr->GetPos();         // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this.
844                         }
845                         bool on_rwy = OnActiveRunway(t->pos);
846                         if(!on_rwy) {
847                                 if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
848                                         rwyList.pop_front();
849                                         delete t;
850                                         // TODO - tell it to taxi / contact ground / don't delete it etc!
851                                 } else if(t->opType == OUTBOUND) {
852                                         depList.push_back(t);
853                                         rwyList.pop_front();
854                                         departed = true;
855                                         timeSinceLastDeparture = 0.0;
856                                 } else if(t->opType == CIRCUIT) {
857                                         circuitList.push_back(t);
858                                         AddToTrafficList(t);
859                                         rwyList.pop_front();
860                                         departed = true;
861                                         timeSinceLastDeparture = 0.0;
862                                 } else if(t->opType == TTT_UNKNOWN) {
863                                         depList.push_back(t);
864                                         circuitList.push_back(t);
865                                         AddToTrafficList(t);
866                                         rwyList.pop_front();
867                                         departed = true;
868                                         timeSinceLastDeparture = 0.0;   // TODO - we need to take into account that the user might taxi-in when flagged opType UNKNOWN - check speed/altitude etc to make decision as to what user is up to.
869                                 } else {
870                                         // HELP - we shouldn't ever get here!!!
871                                 }
872                         }
873                 }
874         }
875         //cout << "Done CheckRunwayList" << endl;
876 }
877
878 // Do one plane from the approach list
879 void FGTower::CheckApproachList(double dt) {
880         if(appList.size()) {
881                 if(appListItr == appList.end()) {
882                         appListItr = appList.begin();
883                 }
884                 TowerPlaneRec* t = *appListItr;
885                 //cout << "t = " << t << endl;
886                 if(t->isUser) {
887                         t->pos.setlon(user_lon_node->getDoubleValue());
888                         t->pos.setlat(user_lat_node->getDoubleValue());
889                         t->pos.setelev(user_elev_node->getDoubleValue());
890                 } else {
891                         // TODO - set/update the position if it's an AI plane
892                 }                               
893                 if(t->nextOnRwy && !(t->clearedToLand)) {
894                         // check distance away and whether runway occupied
895                         // and schedule transmission if necessary
896                 }                               
897                 ++appListItr;
898         }
899 }
900
901 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
902 // plus the constraint position as a rwy orientated orthopos (meters)
903 bool FGTower::GetCrosswindConstraint(double& cpos) {
904         if(crosswind_leg_pos != 0.0) {
905                 cpos = crosswind_leg_pos;
906                 return(true);
907         } else {
908                 cpos = 0.0;
909                 return(false);
910         }
911 }
912 bool FGTower::GetDownwindConstraint(double& dpos) {
913         if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
914                 dpos = downwind_leg_pos;
915                 return(true);
916         } else {
917                 dpos = 0.0;
918                 return(false);
919         }
920 }
921 bool FGTower::GetBaseConstraint(double& bpos) {
922         if(base_leg_pos < nominal_base_leg_pos) {
923                 bpos = base_leg_pos;
924                 return(true);
925         } else {
926                 bpos = nominal_base_leg_pos;
927                 return(false);
928         }
929 }
930
931
932 // Figure out which runways are active.
933 // For now we'll just be simple and do one active runway - eventually this will get much more complex
934 // This is a private function - public interface to the results of this is through GetActiveRunway
935 void FGTower::DoRwyDetails() {
936         //cout << "GetRwyDetails called" << endl;
937         
938         // Based on the airport-id and wind get the active runway
939         
940         //wind
941         double hdg = wind_from_hdg->getDoubleValue();
942         double speed = wind_speed_knots->getDoubleValue();
943         hdg = (speed == 0.0 ? 270.0 : hdg);
944         //cout << "Heading = " << hdg << '\n';
945         
946         FGRunway runway;
947         bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway);
948         if(rwyGood) {
949                 activeRwy = runway.rwy_no;
950                 rwy.rwyID = runway.rwy_no;
951                 SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy);
952                 
953                 // Get the threshold position
954                 double other_way = runway.heading - 180.0;
955                 while(other_way <= 0.0) {
956                         other_way += 360.0;
957                 }
958         // move to the +l end/center of the runway
959                 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
960         Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
961                 Point3D ref = origin;
962         double tshlon, tshlat, tshr;
963                 double tolon, tolat, tor;
964                 rwy.length = runway.length * SG_FEET_TO_METER;
965                 rwy.width = runway.width * SG_FEET_TO_METER;
966         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, 
967                                 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
968         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway.heading, 
969                                 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
970                 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
971                 // now copy what we need out of runway into rwy
972         rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
973                 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
974                 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
975                 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
976                 rwy.hdg = runway.heading;
977                 // Set the projection for the local area based on this active runway
978                 ortho.Init(rwy.threshold_pos, rwy.hdg); 
979                 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
980                 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
981         } else {
982                 SG_LOG(SG_ATC, SG_ALERT, "Help  - can't get good runway in FGTower!!");
983                 activeRwy = "NN";
984         }
985 }
986
987
988 // Figure out if a given position lies on the active runway
989 // Might have to change when we consider more than one active rwy.
990 bool FGTower::OnActiveRunway(Point3D pt) {
991         // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
992         Point3D xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
993         Point3D xyp = ortho.ConvertToLocal(pt);
994         
995         //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
996         //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
997
998         double rlen = rwy.length/2.0 + 5.0;
999         double rwidth = rwy.width/2.0;
1000         double ldiff = fabs(xyp.y() - xyc.y());
1001         double wdiff = fabs(xyp.x() - xyc.x());
1002
1003         return((ldiff < rlen) && (wdiff < rwidth));
1004 }       
1005
1006
1007 // Figure out if a given position lies on any runway or not
1008 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1009 bool FGTower::OnAnyRunway(Point3D pt) {
1010         ATCData ad;
1011         double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 10.0);
1012         if(dist < 0.0) {
1013                 return(false);
1014         }
1015         // Based on the airport-id, go through all the runways and check for a point in them
1016         
1017         // TODO - do we actually need to search for the airport - surely we already know our ident and
1018         // can just search runways of our airport???
1019         //cout << "Airport ident is " << ad.ident << '\n';
1020         FGRunway runway;
1021         bool rwyGood = globals->get_runways()->search(ad.ident, &runway);
1022         if(!rwyGood) {
1023                 SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower");
1024         }
1025         bool on = false;
1026         while(runway.id == ad.ident) {          
1027                 on = OnRunway(pt, runway);
1028                 //cout << "Runway " << runway.rwy_no << ": On = " << (on ? "true\n" : "false\n");
1029                 if(on) return(true);
1030                 globals->get_runways()->next(&runway);          
1031         }
1032         return(on);
1033 }
1034
1035
1036 // Returns true if successful
1037 bool FGTower::RemoveFromTrafficList(string id) {
1038         tower_plane_rec_list_iterator twrItr;
1039         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1040                 TowerPlaneRec* tpr = *twrItr;
1041                 if(tpr->plane.callsign == id) {
1042                         trafficList.erase(twrItr);
1043                         return(true);
1044                 }
1045         }       
1046         SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1047         return(false);
1048 }
1049
1050
1051 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1052 // and set nextOnRwy if so.
1053 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1054 // For planes holding they are put in the first position with time to go, and the return value is
1055 // true if in the first position (nextOnRwy) and false otherwise.
1056 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1057 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1058         //cout << "ADD: " << trafficList.size();
1059         //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1060         double separation_time = 90.0;  // seconds - this is currently a guess for light plane separation, and includes a few seconds for a holding plane to taxi onto the rwy.
1061         double departure_sep_time = 60.0;       // Separation time behind departing airplanes.  Comments above also apply.
1062         bool conflict = false;
1063         double lastETA = 0.0;
1064         bool firstTime = true;
1065         // FIXME - make this more robust for different plane types eg. light following heavy.
1066         tower_plane_rec_list_iterator twrItr;
1067         //twrItr = trafficList.begin();
1068         //while(1) {
1069         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1070                 //if(twrItr == trafficList.end()) {
1071                 //      cout << "  END  ";
1072                 //      trafficList.push_back(t);
1073                 //      return(holding ? firstTime : conflict);
1074                 //} else {
1075                         TowerPlaneRec* tpr = *twrItr;
1076                         if(holding) {
1077                                 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1078                                 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1079                                 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0); 
1080                                 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1081                                 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1082                                 // and if not needs time to clear the rwy anyway.
1083                                 if(tpr->eta  - lastETA > slot_time) {
1084                                         t->nextOnRwy = firstTime;
1085                                         trafficList.insert(twrItr, t);
1086                                         //cout << "\tH\t" << trafficList.size() << '\n';
1087                                         return(firstTime);
1088                                 }
1089                                 firstTime = false;
1090                         } else {
1091                                 if(t->eta < tpr->eta) {
1092                                         // Ugg - this one's tricky.
1093                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1094                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1095                                                 if(tpr->nextOnRwy) {
1096                                                         tpr->nextOnRwy = false;
1097                                                         t->nextOnRwy = true;
1098                                                 }
1099                                                 trafficList.insert(twrItr, t);
1100                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1101                                                 conflict = true;
1102                                                 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1103                                                 if(tpr->nextOnRwy) {
1104                                                         tpr->nextOnRwy = false;
1105                                                         t->nextOnRwy = true;
1106                                                 }
1107                                                 trafficList.insert(twrItr, t);
1108                                         }
1109                                         //cout << "\tC\t" << trafficList.size() << '\n';
1110                                         return(conflict);
1111                                 }
1112                         }
1113                 //}
1114                 //++twrItr;
1115         }
1116         // If we get here we must be at the end of the list, or maybe the list is empty.
1117         if(!trafficList.size()) {
1118                 t->nextOnRwy = true;
1119                 // conflict and firstTime should be false and true respectively in this case anyway.
1120         }
1121         trafficList.push_back(t);
1122         //cout << "\tE\t" << trafficList.size() << endl;
1123         return(holding ? firstTime : conflict);
1124 }
1125
1126 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1127 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1128 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1129         //cout << "ADD: " << circuitList.size();
1130         //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl;
1131         double separation_time = 60.0;  // seconds - this is currently a guess for light plane separation, and includes a few seconds for a holding plane to taxi onto the rwy.
1132         bool conflict = false;
1133         tower_plane_rec_list_iterator twrItr;
1134         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1135                         TowerPlaneRec* tpr = *twrItr;
1136                                 
1137                                 if(t->eta < tpr->eta) {
1138                                         // Ugg - this one's tricky.
1139                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1140                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1141                                                 circuitList.insert(twrItr, t);
1142                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1143                                                 conflict = true;
1144                                                 // HACK - just add anyway for now and flag conflict.
1145                                                 circuitList.insert(twrItr, t);
1146                                         }
1147                                         //cout << "\tC\t" << circuitList.size() << '\n';
1148                                         return(conflict);
1149                                 }
1150         }
1151         // If we get here we must be at the end of the list, or maybe the list is empty.
1152         circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
1153         //cout << "\tE\t" << circuitList.size() << endl;
1154         return(conflict);
1155 }
1156
1157
1158 // Calculate the eta of a plane to the threshold.
1159 // For ground traffic this is the fastest they can get there.
1160 // For air traffic this is the middle approximation.
1161 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1162         // For now we'll be very crude and hardwire expected speeds to C172-like values
1163         // The speeds below are specified in knots IAS and then converted to m/s
1164         double app_ias = 100.0 * 0.514444;                      // Speed during straight-in approach
1165         double circuit_ias = 80.0 * 0.514444;           // Speed around circuit
1166         double final_ias = 70.0 * 0.514444;             // Speed during final approach
1167         
1168         //if(printout) {
1169         //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1170         //cout << (tpr->isUser ? "USER\n" : "AI\n");
1171         //cout << flush;
1172         //}
1173         
1174         // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1175         // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1176         
1177         Point3D op = ortho.ConvertToLocal(tpr->pos);
1178         //if(printout) {
1179         //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1180         //      cout << "opType is " << tpr->opType << '\n';
1181         //}
1182         double dist_out_m = op.y();
1183         double dist_across_m = fabs(op.x());    // FIXME = the fabs is a hack to cope with the fact that we don't know the circuit direction yet
1184         //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1185         
1186         if(tpr->opType == STRAIGHT_IN) {
1187                 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1188                 if(dist_to_go_m < 1000) {
1189                         tpr->eta = dist_to_go_m / final_ias;
1190                 } else {
1191                         tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1192                 }
1193         } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) {       // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1194                 // It's complicated - depends on if base leg is delayed or not
1195                 //if(printout) {
1196                 //      cout << "Leg = " << tpr->leg << '\n';
1197                 //}
1198                 if(tpr->leg == LANDING_ROLL) {
1199                         tpr->eta = 0;
1200                 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1201                         tpr->eta = fabs(dist_out_m) / final_ias;
1202                 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1203                         tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1204                 } else {
1205                         // Need to calculate where base leg is likely to be
1206                         // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1207                         // TODO - as a matter of design - AILocalTraffic should get the nominal no-traffic base turn distance from Tower, since in real life the published pattern might differ from airport to airport
1208                         double nominal_base_dist_out_m = -1000;
1209                         double current_base_dist_out_m;
1210                         if(!GetBaseConstraint(current_base_dist_out_m)) {
1211                                 current_base_dist_out_m = nominal_base_dist_out_m;
1212                         }
1213                         //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1214                         double nominal_dist_across_m = 1000;    // Hardwired value from AILocalTraffic
1215                         double current_dist_across_m;
1216                         if(!GetDownwindConstraint(current_dist_across_m)) {
1217                                 current_dist_across_m = nominal_dist_across_m;
1218                         }
1219                         double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl.
1220                         tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
1221                         //cout << "a = " << tpr->eta << '\n';
1222                         if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1223                                 tpr->eta += dist_across_m / circuit_ias;
1224                                 //cout << "b = " << tpr->eta << '\n';
1225                                 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1226                                 //cout << "c = " << tpr->eta << '\n';
1227                         } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1228                                 if(dist_across_m > nominal_dist_across_m) {
1229                                         tpr->eta += dist_across_m / circuit_ias;
1230                                 } else {
1231                                         tpr->eta += nominal_dist_across_m / circuit_ias;
1232                                 }
1233                                 // should we use the dist across of the previous plane if there is previous still on downwind?
1234                                 //if(printout) cout << "bb = " << tpr->eta << '\n';
1235                                 if(dist_out_m > nominal_cross_dist_out_m) {
1236                                         tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1237                                 } else {
1238                                         tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1239                                 }
1240                                 //if(printout) cout << "cc = " << tpr->eta << '\n';
1241                                 if(nominal_dist_across_m > dist_across_m) {
1242                                         tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1243                                 } else {
1244                                         // Nothing to add
1245                                 }
1246                                 //if(printout) cout << "dd = " << tpr->eta << '\n';
1247                         } else {
1248                                 // We've only just started - why not use a generic estimate?
1249                                 tpr->eta = 240.0;
1250                         }
1251                 }
1252                 //if(printout) {
1253                 //      cout << "ETA = " << tpr->eta << '\n';
1254                 //}
1255                 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1256         } else {
1257                 tpr->eta = 99999;
1258         }       
1259 }
1260
1261
1262 // Calculate the distance of a plane to the threshold in meters
1263 // TODO - Modify to calculate flying distance of a plane in the circuit
1264 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1265         return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1266 }
1267
1268
1269 // Calculate the distance of a plane to the threshold in miles
1270 // TODO - Modify to calculate flying distance of a plane in the circuit
1271 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1272         return(CalcDistOutM(tpr) / 1600.0);             // FIXME - use a proper constant if possible.
1273 }
1274
1275
1276 // Iterate through all the lists and call CalcETA for all the planes.
1277 void FGTower::doThresholdETACalc() {
1278         //cout << "Entering doThresholdETACalc..." << endl;
1279         tower_plane_rec_list_iterator twrItr;
1280         // Do the approach list first
1281         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1282                 TowerPlaneRec* tpr = *twrItr;
1283                 CalcETA(tpr);
1284         }       
1285         // Then the circuit list
1286         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1287                 TowerPlaneRec* tpr = *twrItr;
1288                 CalcETA(tpr);
1289         }
1290         //cout << "Done doThresholdETCCalc" << endl;
1291 }
1292                 
1293
1294 // Check that the planes in traffic list are correctly ordered,
1295 // that the nearest (timewise) is flagged next on rwy, and return
1296 // true if any threshold use conflicts are detected, false otherwise.
1297 bool FGTower::doThresholdUseOrder() {
1298         //cout << "Entering doThresholdUseOrder..." << endl;
1299         bool conflict = false;
1300         
1301         // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1302         // Here's the rather simplistic assumptions we're using:
1303         // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1304         // In order of priority for runway use:
1305         // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1306         // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1307         // and holding planes until a space.
1308         // When calculating if a holding plane can use the runway, time clearance from last departure
1309         // as well as time clearance to next arrival must be considered.
1310         
1311         trafficList.clear();
1312         
1313         tower_plane_rec_list_iterator twrItr;
1314         // Do the approach list first
1315         //cout << "A" << flush;
1316         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1317                 TowerPlaneRec* tpr = *twrItr;
1318                 conflict = AddToTrafficList(tpr);
1319         }       
1320         // Then the circuit list
1321         //cout << "C" << flush;
1322         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1323                 TowerPlaneRec* tpr = *twrItr;
1324                 conflict = AddToTrafficList(tpr);
1325         }
1326         // And finally the hold list
1327         //cout << "H" << endl;
1328         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1329                 TowerPlaneRec* tpr = *twrItr;
1330                 AddToTrafficList(tpr, true);
1331         }
1332         
1333         if(0) {
1334         //if(ident == "KEMT") {
1335                 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1336                         TowerPlaneRec* tpr = *twrItr;
1337                         cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
1338                 }
1339                 cout << endl;
1340         }
1341         
1342         //cout << "Done doThresholdUseOrder" << endl;
1343         return(conflict);
1344 }
1345
1346
1347 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
1348 // i.e. list_pos = 1 implies next to use runway.
1349 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
1350         if(trafficList.size() < list_pos) {
1351                 return(99999);
1352         }
1353
1354         tower_plane_rec_list_iterator twrItr;
1355         twrItr = trafficList.begin();
1356         for(unsigned int i = 1; i < list_pos; i++, twrItr++);
1357         TowerPlaneRec* tpr = *twrItr;
1358         CalcETA(tpr, printout);
1359         //cout << "ETA returned = " << tpr->eta << '\n';
1360         return(tpr->eta);
1361 }
1362         
1363
1364 void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation) {
1365         // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
1366         TowerPlaneRec* t = new TowerPlaneRec;
1367         t->plane = plane;
1368         t->planePtr = requestee;
1369         t->holdShortReported = true;
1370         t->clearedToLineUp = false;
1371         t->clearedToTakeOff = false;
1372         t->opType = operation;
1373         t->pos = requestee->GetPos();
1374         
1375         //cout << "Hold Short reported by " << plane.callsign << '\n';
1376         SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
1377
1378 /*      
1379         bool next = AddToTrafficList(t, true);
1380         if(next) {
1381                 double teta = GetTrafficETA(2);
1382                 if(teta < 150.0) {
1383                         t->clearanceCounter = 7.0;      // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
1384                         //cout << "Reducing response time to request due imminent traffic\n";
1385                 }
1386         } else {
1387         }
1388 */
1389         // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
1390
1391         holdList.push_back(t);
1392         
1393         responseReqd = true;
1394 }
1395
1396 // Register the presence of an AI plane at a point where contact would already have been made in real life
1397 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
1398 void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
1399         // At the moment this is only going to be tested with inserting an AI plane on downwind
1400         TowerPlaneRec* t = new TowerPlaneRec;
1401         t->plane = plane;
1402         t->planePtr = ai;
1403         t->opType = op;
1404         t->leg = lg;
1405         t->pos = ai->GetPos();
1406         
1407         CalcETA(t);
1408         
1409         if(op == CIRCUIT && lg != LEG_UNKNOWN) {
1410                 AddToCircuitList(t);
1411         } else {
1412                 // FLAG A WARNING
1413         }
1414         
1415         doThresholdUseOrder();
1416 }
1417
1418 // Contact tower for VFR approach
1419 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
1420 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
1421 // opt defaults to AIP_LT_UNKNOWN
1422 void FGTower::VFRArrivalContact(string ID, LandingType opt) {
1423         //cout << "Request Landing Clearance called...\n";
1424         
1425         // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
1426
1427         TowerPlaneRec* t;       
1428         string usercall = fgGetString("/sim/user/callsign");
1429         if(ID == "USER" || ID == usercall) {
1430                 t = FindPlane(usercall);
1431                 if(!t) {
1432                         cout << "NOT t\n";
1433                         t = new TowerPlaneRec;
1434                         t->isUser = true;
1435                         t->pos.setlon(user_lon_node->getDoubleValue());
1436                         t->pos.setlat(user_lat_node->getDoubleValue());
1437                         t->pos.setelev(user_elev_node->getDoubleValue());
1438                 } else {
1439                         cout << "IS t\n";
1440                         // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
1441                         // quite getting out of tower airspace - just ignore for now and treat as new arrival.
1442                         // TODO - Maybe should remove from departure and circuit list if in there though!!
1443                 }
1444         } else {
1445                 // Oops - something has gone wrong - put out a warning
1446                 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
1447                 return;
1448         }
1449                 
1450         
1451         // TODO
1452         // Calculate where the plane is in relation to the active runway and it's circuit
1453         // and set the op-type as appropriate.
1454         
1455         // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
1456         t->opType = STRAIGHT_IN;
1457         
1458         t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
1459         t->plane.callsign = usercall;
1460         
1461         t->vfrArrivalReported = true;
1462         responseReqd = true;
1463         
1464         appList.push_back(t);   // Not necessarily permanent
1465         AddToTrafficList(t);
1466 }
1467
1468 void FGTower::RequestDepartureClearance(string ID) {
1469         //cout << "Request Departure Clearance called...\n";
1470 }
1471         
1472 void FGTower::ReportFinal(string ID) {
1473         TowerPlaneRec* t = FindPlane(ID);
1474         if(t) {
1475                 t->finalReported = true;
1476                 t->finalAcknowledged = false;
1477                 if(!(t->clearedToLand)) {
1478                         responseReqd = true;
1479                 } // possibly respond with wind even if already cleared to land?
1480         } else {
1481                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
1482         }
1483 }
1484
1485 //void FGTower::ReportLongFinal(string ID);
1486 //void FGTower::ReportOuterMarker(string ID);
1487 //void FGTower::ReportMiddleMarker(string ID);
1488 //void FGTower::ReportInnerMarker(string ID);
1489 //void FGTower::ReportGoingAround(string ID);
1490
1491 void FGTower::ReportRunwayVacated(string ID) {
1492         //cout << "Report Runway Vacated Called...\n";
1493 }
1494
1495 TowerPlaneRec* FGTower::FindPlane(string ID) {
1496         tower_plane_rec_list_iterator twrItr;
1497         // Do the approach list first
1498         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1499                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1500         }       
1501         // Then the circuit list
1502         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1503                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1504         }
1505         // And finally the hold list
1506         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1507                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1508         }
1509         SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
1510         return(NULL);
1511 }
1512
1513 void FGTower::ReportDownwind(string ID) {
1514         //cout << "ReportDownwind(...) called\n";
1515         // Tell the plane reporting what number she is in the circuit
1516         TowerPlaneRec* t = FindPlane(ID);
1517         if(t) {
1518                 t->downwindReported = true;
1519                 responseReqd = true;
1520         } else {
1521                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
1522         }
1523 }
1524
1525 string FGTower::GenText(const string& m, int c) {
1526         const int cmax = 300;
1527         //string message;
1528         char tag[4];
1529         char crej = '@';
1530         char mes[cmax];
1531         char dum[cmax];
1532         //char buf[10];
1533         char *pos;
1534         int len;
1535         //FGTransmission t;
1536         string usercall = fgGetString("/sim/user/callsign");
1537         
1538         //transmission_list_type     tmissions = transmissionlist_station[station];
1539         //transmission_list_iterator current   = tmissions.begin();
1540         //transmission_list_iterator last      = tmissions.end();
1541         
1542         //for ( ; current != last ; ++current ) {
1543         //      if ( current->get_code().c1 == code.c1 &&  
1544         //              current->get_code().c2 == code.c2 &&
1545         //          current->get_code().c3 == code.c3 ) {
1546                         
1547                         //if ( ttext ) message = current->get_transtext();
1548                         //else message = current->get_menutext();
1549                         strcpy( &mes[0], m.c_str() ); 
1550                         
1551                         // Replace all the '@' parameters with the actual text.
1552                         int check = 0;  // If mes gets overflowed the while loop can go infinite
1553                         while ( strchr(&mes[0], crej) != NULL  ) {      // ie. loop until no more occurances of crej ('@') found
1554                                 pos = strchr( &mes[0], crej );
1555                                 bcopy(pos, &tag[0], 3);
1556                                 tag[3] = '\0';
1557                                 int i;
1558                                 len = 0;
1559                                 for ( i=0; i<cmax; i++ ) {
1560                                         if ( mes[i] == crej ) {
1561                                                 len = i; 
1562                                                 break;
1563                                         }
1564                                 }
1565                                 strncpy( &dum[0], &mes[0], len );
1566                                 dum[len] = '\0';
1567                                 
1568                                 if ( strcmp ( tag, "@ST" ) == 0 )
1569                                         //strcat( &dum[0], tpars.station.c_str() );
1570                                         strcat(&dum[0], ident.c_str());
1571                                 else if ( strcmp ( tag, "@AP" ) == 0 )
1572                                         //strcat( &dum[0], tpars.airport.c_str() );
1573                                         strcat(&dum[0], name.c_str());
1574                                 else if ( strcmp ( tag, "@CS" ) == 0 ) 
1575                                         //strcat( &dum[0], tpars.callsign.c_str() );
1576                                         strcat(&dum[0], usercall.c_str());
1577                                 else if ( strcmp ( tag, "@TD" ) == 0 ) {
1578                                         /*
1579                                         if ( tpars.tdir == 1 ) {
1580                                                 char buf[] = "left";
1581                                                 strcat( &dum[0], &buf[0] );
1582                                         }
1583                                         else {
1584                                                 char buf[] = "right";
1585                                                 strcat( &dum[0], &buf[0] );
1586                                         }
1587                                         */
1588                                 }
1589                                 else if ( strcmp ( tag, "@HE" ) == 0 ) {
1590                                         /*
1591                                         char buf[10];
1592                                         sprintf( buf, "%i", (int)(tpars.heading) );
1593                                         strcat( &dum[0], &buf[0] );
1594                                         */
1595                                 }
1596                                 else if ( strcmp ( tag, "@VD" ) == 0 ) {
1597                                         /*
1598                                         if ( tpars.VDir == 1 ) {
1599                                                 char buf[] = "Descend and maintain";
1600                                                 strcat( &dum[0], &buf[0] );
1601                                         }
1602                                         else if ( tpars.VDir == 2 ) {
1603                                                 char buf[] = "Maintain";
1604                                                 strcat( &dum[0], &buf[0] );
1605                                         }
1606                                         else if ( tpars.VDir == 3 ) {
1607                                                 char buf[] = "Climb and maintain";
1608                                                 strcat( &dum[0], &buf[0] );
1609                                         } 
1610                                         */
1611                                 }
1612                                 else if ( strcmp ( tag, "@AL" ) == 0 ) {
1613                                         /*
1614                                         char buf[10];
1615                                         sprintf( buf, "%i", (int)(tpars.alt) );
1616                                         strcat( &dum[0], &buf[0] );
1617                                         */
1618                                 }
1619                                 else if ( strcmp ( tag, "@MI" ) == 0 ) {
1620                                         char buf[10];
1621                                         //sprintf( buf, "%3.1f", tpars.miles );
1622                                         int dist_miles = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
1623                                         sprintf(buf, "%i", dist_miles);
1624                                         strcat( &dum[0], &buf[0] );
1625                                 }
1626                                 else if ( strcmp ( tag, "@FR" ) == 0 ) {
1627                                         /*
1628                                         char buf[10];
1629                                         sprintf( buf, "%6.2f", tpars.freq );
1630                                         strcat( &dum[0], &buf[0] );
1631                                         */
1632                                 }
1633                                 else if ( strcmp ( tag, "@RW" ) == 0 ) {
1634                                         strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
1635                                 } else if(strcmp(tag, "@CD") == 0) {    // @CD = compass direction
1636                                         double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1637                                         while(h < 0.0) h += 360.0;
1638                                         while(h > 360.0) h -= 360.0;
1639                                         if(h < 22.5 || h > 337.5) {
1640                                                 strcat(&dum[0], "North");
1641                                         } else if(h < 67.5) {
1642                                                 strcat(&dum[0], "North-East");
1643                                         } else if(h < 112.5) {
1644                                                 strcat(&dum[0], "East");
1645                                         } else if(h < 157.5) {
1646                                                 strcat(&dum[0], "South-East");
1647                                         } else if(h < 202.5) {
1648                                                 strcat(&dum[0], "South");
1649                                         } else if(h < 247.5) {
1650                                                 strcat(&dum[0], "South-West");
1651                                         } else if(h < 292.5) {
1652                                                 strcat(&dum[0], "West");
1653                                         } else {
1654                                                 strcat(&dum[0], "North-West");
1655                                         }
1656                                 } else {
1657                                         cout << "Tag " << tag << " not found" << endl;
1658                                         break;
1659                                 }
1660                                 strcat( &dum[0], &mes[len+3] );
1661                                 strcpy( &mes[0], &dum[0] );
1662                                 
1663                                 ++check;
1664                                 if(check > 10) {
1665                                         SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)"); 
1666                                         break;
1667                                 }
1668                         }
1669                         
1670                         //cout << mes  << endl;  
1671                         //break;
1672                 //}
1673         //}
1674         if ( mes != "" ) return mes;
1675         else return "No transmission found";
1676 }
1677
1678 ostream& operator << (ostream& os, tower_traffic_type ttt) {
1679         switch(ttt) {
1680         case(CIRCUIT):      return(os << "CIRCUIT");
1681         case(INBOUND):      return(os << "INBOUND");
1682         case(OUTBOUND):     return(os << "OUTBOUND");
1683         case(TTT_UNKNOWN):  return(os << "UNKNOWN");
1684         case(STRAIGHT_IN):  return(os << "STRAIGHT_IN");
1685         }
1686         return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");
1687 }
1688