1 // FGTower - a class to provide tower control at towered airports.
3 // Written by David Luff, started March 2002.
5 // Copyright (C) 2002 David C. Luff - david.luff@nottingham.ac.uk
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.
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.
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.
21 #include <Main/globals.hxx>
22 #include <Airports/runways.hxx>
23 #include <simgear/math/sg_geodesy.hxx>
24 #include <simgear/debug/logstream.hxx>
27 #include "ATCdisplay.hxx"
29 #include "ATCutils.hxx"
30 #include "ATCDialog.hxx"
31 #include "commlist.hxx"
32 #include "AILocalTraffic.hxx"
38 TowerPlaneRec::TowerPlaneRec() :
40 clearedToLineUp(false),
41 clearedToTakeOff(false),
42 holdShortReported(false),
43 downwindReported(false),
44 longFinalReported(false),
45 longFinalAcknowledged(false),
47 finalAcknowledged(false),
48 rwyVacatedReported(false),
49 rwyVacatedAcknowledged(false),
50 instructedToGoAround(false),
53 vfrArrivalReported(false),
54 vfrArrivalAcknowledged(false),
57 landingType(AIP_LT_UNKNOWN),
60 plane.callsign = "UNKNOWN";
63 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
65 clearedToLineUp(false),
66 clearedToTakeOff(false),
67 holdShortReported(false),
68 downwindReported(false),
69 longFinalReported(false),
70 longFinalAcknowledged(false),
72 finalAcknowledged(false),
73 rwyVacatedReported(false),
74 rwyVacatedAcknowledged(false),
75 instructedToGoAround(false),
78 vfrArrivalReported(false),
79 vfrArrivalAcknowledged(false),
82 landingType(AIP_LT_UNKNOWN),
88 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
90 clearedToLineUp(false),
91 clearedToTakeOff(false),
92 holdShortReported(false),
93 downwindReported(false),
94 longFinalReported(false),
95 longFinalAcknowledged(false),
97 finalAcknowledged(false),
98 rwyVacatedReported(false),
99 rwyVacatedAcknowledged(false),
100 instructedToGoAround(false),
103 vfrArrivalReported(false),
104 vfrArrivalAcknowledged(false),
107 landingType(AIP_LT_UNKNOWN),
110 plane.callsign = "UNKNOWN";
114 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
115 clearedToLand(false),
116 clearedToLineUp(false),
117 clearedToTakeOff(false),
118 holdShortReported(false),
119 downwindReported(false),
120 longFinalReported(false),
121 longFinalAcknowledged(false),
122 finalReported(false),
123 finalAcknowledged(false),
124 rwyVacatedReported(false),
125 rwyVacatedAcknowledged(false),
126 instructedToGoAround(false),
129 vfrArrivalReported(false),
130 vfrArrivalAcknowledged(false),
133 landingType(AIP_LT_UNKNOWN),
143 /*******************************************
146 Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in.
148 Tell AI plane to contact ground when taxiing in.
150 Use track instead of heading to determine what leg of the circuit the user is flying.
152 Use altitude as well as position to try to determine if the user has left the circuit.
154 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and
155 will break when planes start queueing.
157 Implement ReportRunwayVacated
158 *******************************************/
161 ATCmgr = globals->get_ATC_mgr();
163 // Init the property nodes - TODO - need to make sure we're getting surface winds.
164 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
165 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
168 update_count_max = 15;
170 holdListItr = holdList.begin();
171 appListItr = appList.begin();
172 depListItr = depList.begin();
173 rwyListItr = rwyList.begin();
174 circuitListItr = circuitList.begin();
175 trafficListItr = trafficList.begin();
179 timeSinceLastDeparture = 9999;
182 nominal_downwind_leg_pos = 1000.0;
183 nominal_base_leg_pos = -1000.0;
184 // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
187 FGTower::~FGTower() {
188 if(!separateGround) {
193 void FGTower::Init() {
196 // Pointers to user's position
197 user_lon_node = fgGetNode("/position/longitude-deg", true);
198 user_lat_node = fgGetNode("/position/latitude-deg", true);
199 user_elev_node = fgGetNode("/position/altitude-ft", true);
200 user_hdg_node = fgGetNode("/orientation/heading-deg", true);
202 // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user.
203 // I'll punt the startup issue for now though!!!
206 // Setup the ground control at this airport
208 //cout << "Tower ident = " << ident << '\n';
209 if(ATCmgr->GetAirportATCDetails(ident, &a)) {
210 if(a.ground_freq) { // Ground control
211 ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND);
212 separateGround = true;
214 // Something has gone wrong :-(
215 SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-(");
216 ground = new FGGround(ident);
217 separateGround = false;
220 ground->SetDisplay();
222 ground->SetNoDisplay();
226 // Initialise ground anyway to do the shortest path stuff!
227 // Note that we're now responsible for updating and deleting this - NOT the ATCMgr.
228 ground = new FGGround(ident);
229 separateGround = false;
232 ground->SetDisplay();
234 ground->SetNoDisplay();
238 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()");
239 // Initialise ground anyway to avoid segfault later
240 ground = new FGGround(ident);
241 separateGround = false;
244 ground->SetDisplay();
246 ground->SetNoDisplay();
250 // Get the airport elevation
251 aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER;
255 // FIXME - this currently assumes use of the active rwy by the user.
256 rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
258 // Assume the user is started at the threshold ready to take-off
259 TowerPlaneRec* t = new TowerPlaneRec;
260 t->plane.callsign = fgGetString("/sim/user/callsign");
261 t->plane.type = GA_SINGLE; // FIXME - hardwired!!
262 t->opType = TTT_UNKNOWN; // We don't know if the user wants to do circuits or a departure...
263 t->landingType = AIP_LT_UNKNOWN;
264 t->leg = TAKEOFF_ROLL;
267 t->clearedToTakeOff = true;
268 rwyList.push_back(t);
271 // For now assume that this means the user is not at the airport and is in the air.
272 // TODO FIXME - this will break when user starts on apron, at hold short, etc.
273 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);
277 void FGTower::Update(double dt) {
278 //cout << "T" << endl;
279 // Each time step, what do we need to do?
280 // We need to go through the list of outstanding requests and acknowedgements
281 // and process at least one of them.
282 // We need to go through the list of planes under our control and check if
283 // any need to be addressed.
284 // We need to check for planes not under our control coming within our
285 // control area and address if necessary.
287 // TODO - a lot of the below probably doesn't need to be called every frame and should be staggered.
289 // Sort the arriving planes
292 if(ident == "KEMT") {
293 cout << update_count << "\ttL: " << trafficList.size() << " cL: " << circuitList.size() << " hL: " << holdList.size() << " aL: " << appList.size() << '\n';
296 //if(ident == "EGNX") cout << display << '\n';
298 if(departed != false) {
299 timeSinceLastDeparture += dt;
300 //if(ident == "KEMT")
301 // cout << " dt = " << dt << " timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
304 //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n';
306 if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
309 responseReqd = false;
312 // Calculate the eta of each plane to the threshold.
313 // For ground traffic this is the fastest they can get there.
314 // For air traffic this is the middle approximation.
315 if(update_count == 1) {
316 doThresholdETACalc();
319 // Order the list of traffic as per expected threshold use and flag any conflicts
320 if(update_count == 2) {
321 //bool conflicts = doThresholdUseOrder();
322 doThresholdUseOrder();
325 // sortConficts() !!!
327 if(update_count == 4) {
331 // Uggh - HACK - why have we got rwyOccupied - wouldn't simply testing rwyList.size() do?
338 if(update_count == 5 && rwyOccupied) {
342 if(update_count == 6) {
343 CheckCircuitList(dt);
346 if(update_count == 7) {
347 CheckApproachList(dt);
350 // TODO - do one plane from the departure list and set departed = false when out of consideration
354 if(!separateGround) {
355 // The display stuff might have to get more clever than this when not separate
356 // since the tower and ground might try communicating simultaneously even though
357 // they're mean't to be the same contoller/frequency!!
359 ground->SetDisplay();
361 ground->SetNoDisplay();
367 // How big should ii get - ie how long should the update cycle interval stretch?
368 if(update_count >= update_count_max) {
372 // Call the base class update for the response time handling.
375 if(ident == "KEMT") {
376 // For AI debugging convienience - may be removed
378 user_pos.setlon(user_lon_node->getDoubleValue());
379 user_pos.setlat(user_lat_node->getDoubleValue());
380 user_pos.setelev(user_elev_node->getDoubleValue());
381 Point3D user_ortho_pos = ortho.ConvertToLocal(user_pos);
382 fgSetDouble("/AI/user/ortho-x", user_ortho_pos.x());
383 fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y());
384 fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue());
387 //cout << "Done T" << endl;
390 void FGTower::ReceiveUserCallback(int code) {
391 if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
392 cout << "User requested departure\n";
393 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
394 VFRArrivalContact("USER");
395 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
396 VFRArrivalContact("USER", FULL_STOP);
397 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
398 VFRArrivalContact("USER", TOUCH_AND_GO);
399 } else if(code == (int)USER_REPORT_DOWNWIND) {
400 ReportDownwind("USER");
401 } else if(code == (int)USER_REPORT_3_MILE_FINAL) {
402 // For now we'll just call report final instead of report long final to avoid having to alter the response code
404 } else if(code == (int)USER_REPORT_RWY_VACATED) {
405 ReportRunwayVacated("USER");
409 void FGTower::Respond() {
410 cout << "Entering Respond, responseID = " << responseID << endl;
411 TowerPlaneRec* t = FindPlane(responseID);
414 if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
415 // Testing - hardwire straight in for now
416 string trns = t->plane.callsign;
420 // Should we clear staight in or for downwind entry?
421 // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy.
422 // Later on we might check the actual heading and direct some of those to enter on downwind or base.
423 Point3D op = ortho.ConvertToLocal(t->pos);
425 trns += " Report three mile straight-in runway ";
426 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
428 // For now we'll just request reporting downwind.
429 // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used
430 // but I'm not sure how to handle all permutations of which direction to tell to report from yet.
432 trns += (rwy.patternDirection ? "right " : "left ");
433 trns += "downwind runway ";
434 current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
436 trns += ConvertRwyNumToSpokenString(activeRwy);
438 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
440 cout << "Not displaying, trns was " << trns << '\n';
442 t->vfrArrivalAcknowledged = true;
443 } else if(t->downwindReported) {
444 t->downwindReported = false;
446 for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
447 if((*twrItr)->plane.callsign == responseID) break;
450 string trns = t->plane.callsign;
452 trns += ConvertNumToSpokenDigits(i);
455 trns += "Cleared to land";
456 t->clearedToLand = true;
459 globals->get_ATC_display()->RegisterSingleMessage(trns);
462 if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT;
463 current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
465 } else if(t->holdShortReported) {
467 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!!)
468 // Do nothing for now - consider acknowloging hold short eventually
470 ClearHoldingPlane(t);
471 t->leg = TAKEOFF_ROLL;
472 rwyList.push_back(t);
474 // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
475 // FIXME TODO - FIX THIS!!!
476 if(holdList.size()) {
477 if(holdListItr == holdList.end()) {
478 holdListItr = holdList.begin();
480 holdList.erase(holdListItr);
481 holdListItr = holdList.begin();
485 // Tell him to hold and what position he is.
486 // Not currently sure under which circumstances we do or don't bother transmitting this.
487 string trns = t->plane.callsign;
488 trns += " hold position";
490 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
492 // TODO - add some idea of what traffic is blocking him.
494 t->holdShortReported = false;
495 } else if(t->finalReported && !(t->finalAcknowledged)) {
497 string trns = t->plane.callsign;
498 cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
499 cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
500 if(t->nextOnRwy && !rwyOccupied) {
501 if(t->landingType == FULL_STOP) {
502 trns += " cleared to land ";
504 trns += " cleared for the option ";
507 t->clearedToLand = true;
508 if(t->isUser) current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
509 } else if(t->eta < 20) {
510 // Do nothing - we'll be telling it to go around in less than 10 seconds if the
511 // runway doesn't clear so no point in calling "continue approach".
514 trns += " continue approach";
515 t->clearedToLand = false;
517 if(display && disp) {
518 globals->get_ATC_display()->RegisterSingleMessage(trns);
520 t->finalAcknowledged = true;
521 } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
522 string trns = t->plane.callsign;
524 trns += " Contact ground on ";
525 double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0;
527 sprintf(buf, "%.2f", f);
532 trns += " cleared for taxi to the GA parking";
535 globals->get_ATC_display()->RegisterSingleMessage(trns);
537 t->rwyVacatedAcknowledged = true;
538 // Maybe we should check that the plane really *has* vacated the runway!
541 freqClear = true; // FIXME - set this to come true after enough time to render the message
542 //cout << "Done Respond" << endl;
545 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land -
546 // this should be done prior to calling this function.
547 void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
548 //cout << "Entering ClearHoldingPlane..." << endl;
550 string trns = t->plane.callsign;
551 //if(departed plane < some threshold in time away) {
553 //if(timeSinceLastDeparture <= 60.0 && departed == true) {
555 t->clearedToLineUp = true;
556 t->planePtr->RegisterTransmission(3); // cleared to line-up
557 //} else if(arriving plane < some threshold away) {
558 } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) { // Hack - hardwired time
559 trns += " cleared immediate take-off";
560 if(trafficList.size()) {
561 tower_plane_rec_list_iterator trfcItr = trafficList.begin();
562 trfcItr++; // At the moment the holding plane should be first in trafficList.
563 // Note though that this will break if holding planes aren't put in trafficList in the future.
564 TowerPlaneRec* trfc = *trfcItr;
565 trns += "... traffic is";
566 switch(trfc->plane.type) {
570 trns += " a Cessna"; // TODO - add ability to specify actual plane type somewhere
576 trns += " a King-air";
579 trns += " a Learjet";
582 trns += " a Regional";
591 //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
592 if(trfc->opType == STRAIGHT_IN) {
593 double miles_out = CalcDistOutMiles(trfc);
598 trns += ConvertNumToSpokenDigits((int)miles_out);
599 trns += " mile final";
601 } else if(trfc->opType == CIRCUIT) {
602 //cout << "Getting leg of " << trfc->plane.callsign << '\n';
608 trns += " turning final";
614 trns += " turning base";
617 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.
619 // And to eliminate compiler warnings...
620 case TAKEOFF_ROLL: break;
621 case CLIMBOUT: break;
623 case CROSSWIND: break;
625 case LANDING_ROLL: break;
626 case LEG_UNKNOWN: break;
630 // By definition there should be some arriving traffic if we're cleared for immediate takeoff
631 SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
633 t->clearedToTakeOff = true;
634 t->planePtr->RegisterTransmission(4); // cleared to take-off - TODO differentiate between immediate and normal take-off
636 timeSinceLastDeparture = 0.0;
638 //} else if(timeSinceLastDeparture > 60.0 || departed == false) { // Hack - test for timeSinceLastDeparture should be in lineup block eventually
639 trns += " cleared for take-off";
640 // TODO - add traffic is... ?
641 t->clearedToTakeOff = true;
642 t->planePtr->RegisterTransmission(4); // cleared to take-off
644 timeSinceLastDeparture = 0.0;
647 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
649 //cout << "Done ClearHoldingPlane " << endl;
652 // Do one plane from the hold list
653 void FGTower::CheckHoldList(double dt) {
654 //cout << "Entering CheckHoldList..." << endl;
655 if(holdList.size()) {
656 //cout << "*holdListItr = " << *holdListItr << endl;
657 if(holdListItr == holdList.end()) {
658 holdListItr = holdList.begin();
660 //cout << "*holdListItr = " << *holdListItr << endl;
661 //Process(*holdListItr);
662 TowerPlaneRec* t = *holdListItr;
663 //cout << "t = " << t << endl;
664 if(t->holdShortReported) {
665 // NO-OP - leave it to the response handler.
666 } else { // not responding to report, but still need to clear if clear
668 //cout << "departed = " << departed << '\n';
669 //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
672 } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
673 // Do nothing - this is a bit of a hack - should maybe do line up be ready here
675 ClearHoldingPlane(t);
676 t->leg = TAKEOFF_ROLL;
677 rwyList.push_back(t);
679 holdList.erase(holdListItr);
680 holdListItr = holdList.begin();
683 // TODO - rationalise the considerable code duplication above!
687 //cout << "Done CheckHoldList" << endl;
690 // do the ciruit list
691 void FGTower::CheckCircuitList(double dt) {
692 //cout << "Entering CheckCircuitList..." << endl;
693 // Clear the constraints - we recalculate here.
695 downwind_leg_pos = 0.0;
696 crosswind_leg_pos = 0.0;
698 if(circuitList.size()) { // Do one plane from the circuit
699 if(circuitListItr == circuitList.end()) {
700 circuitListItr = circuitList.begin();
702 TowerPlaneRec* t = *circuitListItr;
704 t->pos.setlon(user_lon_node->getDoubleValue());
705 t->pos.setlat(user_lat_node->getDoubleValue());
706 t->pos.setelev(user_elev_node->getDoubleValue());
708 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.
709 t->landingType = t->planePtr->GetLandingOption();
710 //cout << "AI plane landing option is " << t->landingType << '\n';
712 Point3D tortho = ortho.ConvertToLocal(t->pos);
714 // Need to figure out which leg he's on
715 //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue();
716 double ho = GetAngleDiff_deg(user_hdg_node->getDoubleValue(), rwy.hdg);
717 //cout << " ho = " << ho << " abs(ho = " << abs(ho) << '\n';
718 // TODO FIXME - get the wind and convert this to track, or otherwise use track somehow!!!
719 // If it's gusty might need to filter the value, although we are leaving 30 degrees each way leeway!
721 // could be either takeoff, climbout or landing - check orthopos.y
722 //cout << "tortho.y = " << tortho.y() << '\n';
723 if((tortho.y() < 0) || (t->leg == TURN4) || (t->leg == FINAL)) {
727 t->leg = CLIMBOUT; // TODO - check elev wrt. apt elev to differentiate takeoff roll and climbout
728 //cout << "Climbout\n";
729 // If it's the user we may be unsure of his/her intentions.
730 // (Hopefully the AI planes won't try confusing the sim!!!)
731 if(t->opType == TTT_UNKNOWN) {
732 if(tortho.y() > 5000) {
733 // 5 km out from threshold - assume it's a departure
734 t->opType = OUTBOUND; // TODO - could check if the user has climbed significantly above circuit altitude as well.
735 // Since we are unknown operation we should be in depList already.
736 circuitList.erase(circuitListItr);
737 RemoveFromTrafficList(t->plane.callsign);
738 circuitListItr = circuitList.begin();
740 } else if(t->opType == CIRCUIT) {
741 if(tortho.y() > 10000) {
742 // 10 km out - assume the user has abandoned the circuit!!
743 t->opType = OUTBOUND;
744 depList.push_back(t);
745 circuitList.erase(circuitListItr);
746 circuitListItr = circuitList.begin();
750 } else if(abs(ho) < 60) {
752 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
753 if((t->leg == CLIMBOUT) || (t->leg == TURN1)) {
760 } else if(abs(ho) < 120) {
762 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
763 if((t->leg == TURN1) || (t->leg == CROSSWIND)) {
765 //cout << "Crosswind\n";
770 } else if(abs(ho) < 150) {
772 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
773 if((t->leg == CROSSWIND) || (t->leg == TURN2)) {
778 // Probably safe now to assume the user is flying a circuit
785 //cout << "Downwind\n";
787 if(t->leg == FINAL) {
788 if(OnActiveRunway(t->pos)) {
789 t->leg = LANDING_ROLL;
793 t->leg = t->planePtr->GetLeg();
796 // Set the constraints IF this is the first plane in the circuit
797 // 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!!
798 if(circuitListItr == circuitList.begin()) {
801 // 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.
802 base_leg_pos = tortho.y();
803 //cout << "base_leg_pos = " << base_leg_pos << '\n';
806 // Fall through to base
808 base_leg_pos = tortho.y();
809 //cout << "base_leg_pos = " << base_leg_pos << '\n';
812 // Fall through to downwind
814 // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
815 base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
816 //cout << "base_leg_pos = " << base_leg_pos;
817 downwind_leg_pos = tortho.x(); // Assume that a following plane can simply be constrained by the immediately in front downwind plane
818 //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
821 // Fall through to crosswind
823 crosswind_leg_pos = tortho.y();
824 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
825 t->instructedToGoAround = false;
828 // Fall through to climbout
830 // Only use current by constraint as largest
831 crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
832 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
845 if(t->leg == FINAL) {
846 if(t->landingType == FULL_STOP) t->opType = INBOUND;
847 if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
848 // TODO - need to make this more sophisticated
849 // eg. is the plane accelerating down the runway taking off [OK],
850 // or stationary near the start [V. BAD!!].
851 // For now this should stop the AI plane landing on top of the user.
852 string trns = t->plane.callsign;
853 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
855 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
857 t->instructedToGoAround = true;
859 cout << "Registering Go-around transmission with AI plane\n";
860 t->planePtr->RegisterTransmission(13);
863 } else if(t->leg == LANDING_ROLL) {
864 rwyList.push_front(t);
865 // TODO - if(!clearedToLand) shout something!!
866 t->clearedToLand = false;
867 RemoveFromTrafficList(t->plane.callsign);
869 t->opType = TTT_UNKNOWN;
870 } // TODO - allow the user to specify opType via ATC menu
871 circuitListItr = circuitList.erase(circuitListItr);
872 if(circuitListItr == circuitList.end() ) {
873 circuitListItr = circuitList.begin();
878 //cout << "Done CheckCircuitList" << endl;
881 // 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!!
882 // FIXME - at the moment it looks like we're only doing the first plane from the rwy list.
883 // (However, at the moment there should only be one airplane on the rwy at once, until we
884 // start allowing planes to line up whilst previous arrival clears the rwy.)
885 void FGTower::CheckRunwayList(double dt) {
886 //cout << "Entering CheckRunwayList..." << endl;
888 if(!rwyList.size()) {
891 rwyListItr = rwyList.begin();
892 TowerPlaneRec* t = *rwyListItr;
894 t->pos.setlon(user_lon_node->getDoubleValue());
895 t->pos.setlat(user_lat_node->getDoubleValue());
896 t->pos.setelev(user_elev_node->getDoubleValue());
898 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.
900 bool on_rwy = OnActiveRunway(t->pos);
902 if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
905 // TODO - tell it to taxi / contact ground / don't delete it etc!
906 } else if(t->opType == OUTBOUND) {
907 depList.push_back(t);
910 timeSinceLastDeparture = 0.0;
911 } else if(t->opType == CIRCUIT) {
912 circuitList.push_back(t);
916 timeSinceLastDeparture = 0.0;
917 } else if(t->opType == TTT_UNKNOWN) {
918 depList.push_back(t);
919 circuitList.push_back(t);
923 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.
925 // HELP - we shouldn't ever get here!!!
930 //cout << "Done CheckRunwayList" << endl;
933 // Do one plane from the approach list
934 void FGTower::CheckApproachList(double dt) {
936 if(appListItr == appList.end()) {
937 appListItr = appList.begin();
939 TowerPlaneRec* t = *appListItr;
940 //cout << "t = " << t << endl;
942 t->pos.setlon(user_lon_node->getDoubleValue());
943 t->pos.setlat(user_lat_node->getDoubleValue());
944 t->pos.setelev(user_elev_node->getDoubleValue());
946 // TODO - set/update the position if it's an AI plane
948 if(t->nextOnRwy && !(t->clearedToLand)) {
949 // check distance away and whether runway occupied
950 // and schedule transmission if necessary
956 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
957 // plus the constraint position as a rwy orientated orthopos (meters)
958 bool FGTower::GetCrosswindConstraint(double& cpos) {
959 if(crosswind_leg_pos != 0.0) {
960 cpos = crosswind_leg_pos;
967 bool FGTower::GetDownwindConstraint(double& dpos) {
968 if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
969 dpos = downwind_leg_pos;
976 bool FGTower::GetBaseConstraint(double& bpos) {
977 if(base_leg_pos < nominal_base_leg_pos) {
981 bpos = nominal_base_leg_pos;
987 // Figure out which runways are active.
988 // For now we'll just be simple and do one active runway - eventually this will get much more complex
989 // This is a private function - public interface to the results of this is through GetActiveRunway
990 void FGTower::DoRwyDetails() {
991 //cout << "GetRwyDetails called" << endl;
993 // Based on the airport-id and wind get the active runway
996 double hdg = wind_from_hdg->getDoubleValue();
997 double speed = wind_speed_knots->getDoubleValue();
998 hdg = (speed == 0.0 ? 270.0 : hdg);
999 //cout << "Heading = " << hdg << '\n';
1002 bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway);
1004 activeRwy = runway.rwy_no;
1005 rwy.rwyID = runway.rwy_no;
1006 SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy);
1008 // Get the threshold position
1009 double other_way = runway.heading - 180.0;
1010 while(other_way <= 0.0) {
1013 // move to the +l end/center of the runway
1014 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
1015 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
1016 Point3D ref = origin;
1017 double tshlon, tshlat, tshr;
1018 double tolon, tolat, tor;
1019 rwy.length = runway.length * SG_FEET_TO_METER;
1020 rwy.width = runway.width * SG_FEET_TO_METER;
1021 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
1022 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
1023 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway.heading,
1024 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
1025 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
1026 // now copy what we need out of runway into rwy
1027 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
1028 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
1029 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
1030 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
1031 rwy.hdg = runway.heading;
1032 // Set the projection for the local area based on this active runway
1033 ortho.Init(rwy.threshold_pos, rwy.hdg);
1034 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
1035 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
1037 // Set the pattern direction
1038 // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
1039 // for certain circumstances (RH parallel rwy).
1040 rwy.patternDirection = -1; // Left
1041 if(rwy.rwyID.size() == 3) {
1042 rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
1045 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGTower!!");
1051 // Figure out if a given position lies on the active runway
1052 // Might have to change when we consider more than one active rwy.
1053 bool FGTower::OnActiveRunway(Point3D pt) {
1054 // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
1055 Point3D xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
1056 Point3D xyp = ortho.ConvertToLocal(pt);
1058 //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
1059 //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
1061 double rlen = rwy.length/2.0 + 5.0;
1062 double rwidth = rwy.width/2.0;
1063 double ldiff = fabs(xyp.y() - xyc.y());
1064 double wdiff = fabs(xyp.x() - xyc.x());
1066 return((ldiff < rlen) && (wdiff < rwidth));
1070 // Figure out if a given position lies on any runway or not
1071 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1072 bool FGTower::OnAnyRunway(Point3D pt) {
1074 double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 10.0);
1078 // Based on the airport-id, go through all the runways and check for a point in them
1080 // TODO - do we actually need to search for the airport - surely we already know our ident and
1081 // can just search runways of our airport???
1082 //cout << "Airport ident is " << ad.ident << '\n';
1084 bool rwyGood = globals->get_runways()->search(ad.ident, &runway);
1086 SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower");
1089 while(runway.id == ad.ident) {
1090 on = OnRunway(pt, runway);
1091 //cout << "Runway " << runway.rwy_no << ": On = " << (on ? "true\n" : "false\n");
1092 if(on) return(true);
1093 globals->get_runways()->next(&runway);
1099 // Returns true if successful
1100 bool FGTower::RemoveFromTrafficList(string id) {
1101 tower_plane_rec_list_iterator twrItr;
1102 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1103 TowerPlaneRec* tpr = *twrItr;
1104 if(tpr->plane.callsign == id) {
1105 trafficList.erase(twrItr);
1109 SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1114 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1115 // and set nextOnRwy if so.
1116 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1117 // For planes holding they are put in the first position with time to go, and the return value is
1118 // true if in the first position (nextOnRwy) and false otherwise.
1119 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1120 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1121 //cout << "ADD: " << trafficList.size();
1122 //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1123 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.
1124 double departure_sep_time = 60.0; // Separation time behind departing airplanes. Comments above also apply.
1125 bool conflict = false;
1126 double lastETA = 0.0;
1127 bool firstTime = true;
1128 // FIXME - make this more robust for different plane types eg. light following heavy.
1129 tower_plane_rec_list_iterator twrItr;
1130 //twrItr = trafficList.begin();
1132 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1133 //if(twrItr == trafficList.end()) {
1135 // trafficList.push_back(t);
1136 // return(holding ? firstTime : conflict);
1138 TowerPlaneRec* tpr = *twrItr;
1140 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1141 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1142 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0);
1143 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1144 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1145 // and if not needs time to clear the rwy anyway.
1146 if(tpr->eta - lastETA > slot_time) {
1147 t->nextOnRwy = firstTime;
1148 trafficList.insert(twrItr, t);
1149 //cout << "\tH\t" << trafficList.size() << '\n';
1154 if(t->eta < tpr->eta) {
1155 // Ugg - this one's tricky.
1156 // It depends on what the two planes are doing and whether there's a conflict what we do.
1157 if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1158 if(tpr->nextOnRwy) {
1159 tpr->nextOnRwy = false;
1160 t->nextOnRwy = true;
1162 trafficList.insert(twrItr, t);
1163 } else { // Ooops - this ones tricky - we have a potential conflict!
1165 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1166 if(tpr->nextOnRwy) {
1167 tpr->nextOnRwy = false;
1168 t->nextOnRwy = true;
1170 trafficList.insert(twrItr, t);
1172 //cout << "\tC\t" << trafficList.size() << '\n';
1179 // If we get here we must be at the end of the list, or maybe the list is empty.
1180 if(!trafficList.size()) {
1181 t->nextOnRwy = true;
1182 // conflict and firstTime should be false and true respectively in this case anyway.
1184 trafficList.push_back(t);
1185 //cout << "\tE\t" << trafficList.size() << endl;
1186 return(holding ? firstTime : conflict);
1189 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1190 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1191 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1192 //cout << "ADD: " << circuitList.size();
1193 //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl;
1194 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.
1195 bool conflict = false;
1196 tower_plane_rec_list_iterator twrItr;
1197 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1198 TowerPlaneRec* tpr = *twrItr;
1200 if(t->eta < tpr->eta) {
1201 // Ugg - this one's tricky.
1202 // It depends on what the two planes are doing and whether there's a conflict what we do.
1203 if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1204 circuitList.insert(twrItr, t);
1205 } else { // Ooops - this ones tricky - we have a potential conflict!
1207 // HACK - just add anyway for now and flag conflict.
1208 circuitList.insert(twrItr, t);
1210 //cout << "\tC\t" << circuitList.size() << '\n';
1214 // If we get here we must be at the end of the list, or maybe the list is empty.
1215 circuitList.push_back(t); // TODO - check the separation with the preceding plane for the conflict flag.
1216 //cout << "\tE\t" << circuitList.size() << endl;
1221 // Calculate the eta of a plane to the threshold.
1222 // For ground traffic this is the fastest they can get there.
1223 // For air traffic this is the middle approximation.
1224 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1225 // For now we'll be very crude and hardwire expected speeds to C172-like values
1226 // The speeds below are specified in knots IAS and then converted to m/s
1227 double app_ias = 100.0 * 0.514444; // Speed during straight-in approach
1228 double circuit_ias = 80.0 * 0.514444; // Speed around circuit
1229 double final_ias = 70.0 * 0.514444; // Speed during final approach
1232 //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1233 //cout << (tpr->isUser ? "USER\n" : "AI\n");
1237 // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1238 // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1240 Point3D op = ortho.ConvertToLocal(tpr->pos);
1242 //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1243 // cout << "opType is " << tpr->opType << '\n';
1245 double dist_out_m = op.y();
1246 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
1247 //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1249 if(tpr->opType == STRAIGHT_IN) {
1250 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1251 if(dist_to_go_m < 1000) {
1252 tpr->eta = dist_to_go_m / final_ias;
1254 tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1256 } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) { // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1257 // It's complicated - depends on if base leg is delayed or not
1259 // cout << "Leg = " << tpr->leg << '\n';
1261 if(tpr->leg == LANDING_ROLL) {
1263 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1264 tpr->eta = fabs(dist_out_m) / final_ias;
1265 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1266 tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1268 // Need to calculate where base leg is likely to be
1269 // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1270 // 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
1271 double nominal_base_dist_out_m = -1000;
1272 double current_base_dist_out_m;
1273 if(!GetBaseConstraint(current_base_dist_out_m)) {
1274 current_base_dist_out_m = nominal_base_dist_out_m;
1276 //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1277 double nominal_dist_across_m = 1000; // Hardwired value from AILocalTraffic
1278 double current_dist_across_m;
1279 if(!GetDownwindConstraint(current_dist_across_m)) {
1280 current_dist_across_m = nominal_dist_across_m;
1282 double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl.
1283 tpr->eta = fabs(current_base_dist_out_m) / final_ias; // final
1284 //cout << "a = " << tpr->eta << '\n';
1285 if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1286 tpr->eta += dist_across_m / circuit_ias;
1287 //cout << "b = " << tpr->eta << '\n';
1288 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1289 //cout << "c = " << tpr->eta << '\n';
1290 } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1291 if(dist_across_m > nominal_dist_across_m) {
1292 tpr->eta += dist_across_m / circuit_ias;
1294 tpr->eta += nominal_dist_across_m / circuit_ias;
1296 // should we use the dist across of the previous plane if there is previous still on downwind?
1297 //if(printout) cout << "bb = " << tpr->eta << '\n';
1298 if(dist_out_m > nominal_cross_dist_out_m) {
1299 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1301 tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1303 //if(printout) cout << "cc = " << tpr->eta << '\n';
1304 if(nominal_dist_across_m > dist_across_m) {
1305 tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1309 //if(printout) cout << "dd = " << tpr->eta << '\n';
1311 // We've only just started - why not use a generic estimate?
1316 // cout << "ETA = " << tpr->eta << '\n';
1318 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1325 // Calculate the distance of a plane to the threshold in meters
1326 // TODO - Modify to calculate flying distance of a plane in the circuit
1327 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1328 return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1332 // Calculate the distance of a plane to the threshold in miles
1333 // TODO - Modify to calculate flying distance of a plane in the circuit
1334 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1335 return(CalcDistOutM(tpr) / 1600.0); // FIXME - use a proper constant if possible.
1339 // Iterate through all the lists and call CalcETA for all the planes.
1340 void FGTower::doThresholdETACalc() {
1341 //cout << "Entering doThresholdETACalc..." << endl;
1342 tower_plane_rec_list_iterator twrItr;
1343 // Do the approach list first
1344 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1345 TowerPlaneRec* tpr = *twrItr;
1348 // Then the circuit list
1349 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1350 TowerPlaneRec* tpr = *twrItr;
1353 //cout << "Done doThresholdETCCalc" << endl;
1357 // Check that the planes in traffic list are correctly ordered,
1358 // that the nearest (timewise) is flagged next on rwy, and return
1359 // true if any threshold use conflicts are detected, false otherwise.
1360 bool FGTower::doThresholdUseOrder() {
1361 //cout << "Entering doThresholdUseOrder..." << endl;
1362 bool conflict = false;
1364 // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1365 // Here's the rather simplistic assumptions we're using:
1366 // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1367 // In order of priority for runway use:
1368 // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1369 // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1370 // and holding planes until a space.
1371 // When calculating if a holding plane can use the runway, time clearance from last departure
1372 // as well as time clearance to next arrival must be considered.
1374 trafficList.clear();
1376 tower_plane_rec_list_iterator twrItr;
1377 // Do the approach list first
1378 //cout << "A" << flush;
1379 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1380 TowerPlaneRec* tpr = *twrItr;
1381 conflict = AddToTrafficList(tpr);
1383 // Then the circuit list
1384 //cout << "C" << flush;
1385 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1386 TowerPlaneRec* tpr = *twrItr;
1387 conflict = AddToTrafficList(tpr);
1389 // And finally the hold list
1390 //cout << "H" << endl;
1391 for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1392 TowerPlaneRec* tpr = *twrItr;
1393 AddToTrafficList(tpr, true);
1397 //if(ident == "KEMT") {
1398 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1399 TowerPlaneRec* tpr = *twrItr;
1400 cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
1405 //cout << "Done doThresholdUseOrder" << endl;
1410 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
1411 // i.e. list_pos = 1 implies next to use runway.
1412 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
1413 if(trafficList.size() < list_pos) {
1417 tower_plane_rec_list_iterator twrItr;
1418 twrItr = trafficList.begin();
1419 for(unsigned int i = 1; i < list_pos; i++, twrItr++);
1420 TowerPlaneRec* tpr = *twrItr;
1421 CalcETA(tpr, printout);
1422 //cout << "ETA returned = " << tpr->eta << '\n';
1427 void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation) {
1428 // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
1429 TowerPlaneRec* t = new TowerPlaneRec;
1431 t->planePtr = requestee;
1432 t->holdShortReported = true;
1433 t->clearedToLineUp = false;
1434 t->clearedToTakeOff = false;
1435 t->opType = operation;
1436 t->pos = requestee->GetPos();
1438 //cout << "Hold Short reported by " << plane.callsign << '\n';
1439 SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
1442 bool next = AddToTrafficList(t, true);
1444 double teta = GetTrafficETA(2);
1446 t->clearanceCounter = 7.0; // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
1447 //cout << "Reducing response time to request due imminent traffic\n";
1452 // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
1454 holdList.push_back(t);
1456 responseReqd = true;
1459 // Register the presence of an AI plane at a point where contact would already have been made in real life
1460 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
1461 void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
1462 // At the moment this is only going to be tested with inserting an AI plane on downwind
1463 TowerPlaneRec* t = new TowerPlaneRec;
1468 t->pos = ai->GetPos();
1472 if(op == CIRCUIT && lg != LEG_UNKNOWN) {
1473 AddToCircuitList(t);
1478 doThresholdUseOrder();
1481 // Contact tower for VFR approach
1482 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
1483 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
1484 // opt defaults to AIP_LT_UNKNOWN
1485 void FGTower::VFRArrivalContact(string ID, LandingType opt) {
1486 //cout << "Request Landing Clearance called...\n";
1488 // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
1491 string usercall = fgGetString("/sim/user/callsign");
1492 if(ID == "USER" || ID == usercall) {
1493 t = FindPlane(usercall);
1495 //cout << "NOT t\n";
1496 t = new TowerPlaneRec;
1498 t->pos.setlon(user_lon_node->getDoubleValue());
1499 t->pos.setlat(user_lat_node->getDoubleValue());
1500 t->pos.setelev(user_elev_node->getDoubleValue());
1503 // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
1504 // quite getting out of tower airspace - just ignore for now and treat as new arrival.
1505 // TODO - Maybe should remove from departure and circuit list if in there though!!
1508 // Oops - something has gone wrong - put out a warning
1509 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
1515 // Calculate where the plane is in relation to the active runway and it's circuit
1516 // and set the op-type as appropriate.
1518 // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
1519 t->opType = STRAIGHT_IN;
1521 t->plane.type = GA_SINGLE; // FIXME - Another assumption!
1522 t->plane.callsign = usercall;
1524 t->vfrArrivalReported = true;
1525 responseReqd = true;
1527 appList.push_back(t); // Not necessarily permanent
1528 AddToTrafficList(t);
1530 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1531 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1532 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1535 void FGTower::RequestDepartureClearance(string ID) {
1536 //cout << "Request Departure Clearance called...\n";
1539 void FGTower::ReportFinal(string ID) {
1541 ID = fgGetString("/sim/user/callsign");
1542 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1544 TowerPlaneRec* t = FindPlane(ID);
1546 t->finalReported = true;
1547 t->finalAcknowledged = false;
1548 if(!(t->clearedToLand)) {
1549 responseReqd = true;
1550 } // possibly respond with wind even if already cleared to land?
1552 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
1556 void FGTower::ReportLongFinal(string ID) {
1558 ID = fgGetString("/sim/user/callsign");
1559 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1561 TowerPlaneRec* t = FindPlane(ID);
1563 t->longFinalReported = true;
1564 t->longFinalAcknowledged = false;
1565 if(!(t->clearedToLand)) {
1566 responseReqd = true;
1567 } // possibly respond with wind even if already cleared to land?
1569 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
1573 //void FGTower::ReportOuterMarker(string ID);
1574 //void FGTower::ReportMiddleMarker(string ID);
1575 //void FGTower::ReportInnerMarker(string ID);
1576 //void FGTower::ReportGoingAround(string ID);
1578 void FGTower::ReportRunwayVacated(string ID) {
1579 //cout << "Report Runway Vacated Called...\n";
1581 ID = fgGetString("/sim/user/callsign");
1582 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1584 TowerPlaneRec* t = FindPlane(ID);
1586 t->rwyVacatedReported = true;
1587 responseReqd = true;
1589 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
1593 TowerPlaneRec* FGTower::FindPlane(string ID) {
1594 tower_plane_rec_list_iterator twrItr;
1595 // Do the approach list first
1596 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1597 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1599 // Then the circuit list
1600 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1601 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1603 // And finally the hold list
1604 for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1605 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1607 SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
1611 void FGTower::ReportDownwind(string ID) {
1612 //cout << "ReportDownwind(...) called\n";
1614 ID = fgGetString("/sim/user/callsign");
1615 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
1617 TowerPlaneRec* t = FindPlane(ID);
1619 t->downwindReported = true;
1620 responseReqd = true;
1622 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
1626 string FGTower::GenText(const string& m, int c) {
1627 const int cmax = 300;
1637 string usercall = fgGetString("/sim/user/callsign");
1639 //transmission_list_type tmissions = transmissionlist_station[station];
1640 //transmission_list_iterator current = tmissions.begin();
1641 //transmission_list_iterator last = tmissions.end();
1643 //for ( ; current != last ; ++current ) {
1644 // if ( current->get_code().c1 == code.c1 &&
1645 // current->get_code().c2 == code.c2 &&
1646 // current->get_code().c3 == code.c3 ) {
1648 //if ( ttext ) message = current->get_transtext();
1649 //else message = current->get_menutext();
1650 strcpy( &mes[0], m.c_str() );
1652 // Replace all the '@' parameters with the actual text.
1653 int check = 0; // If mes gets overflowed the while loop can go infinite
1654 while ( strchr(&mes[0], crej) != NULL ) { // ie. loop until no more occurances of crej ('@') found
1655 pos = strchr( &mes[0], crej );
1656 bcopy(pos, &tag[0], 3);
1660 for ( i=0; i<cmax; i++ ) {
1661 if ( mes[i] == crej ) {
1666 strncpy( &dum[0], &mes[0], len );
1669 if ( strcmp ( tag, "@ST" ) == 0 )
1670 //strcat( &dum[0], tpars.station.c_str() );
1671 strcat(&dum[0], ident.c_str());
1672 else if ( strcmp ( tag, "@AP" ) == 0 )
1673 //strcat( &dum[0], tpars.airport.c_str() );
1674 strcat(&dum[0], name.c_str());
1675 else if ( strcmp ( tag, "@CS" ) == 0 )
1676 //strcat( &dum[0], tpars.callsign.c_str() );
1677 strcat(&dum[0], usercall.c_str());
1678 else if ( strcmp ( tag, "@TD" ) == 0 ) {
1680 if ( tpars.tdir == 1 ) {
1681 char buf[] = "left";
1682 strcat( &dum[0], &buf[0] );
1685 char buf[] = "right";
1686 strcat( &dum[0], &buf[0] );
1690 else if ( strcmp ( tag, "@HE" ) == 0 ) {
1693 sprintf( buf, "%i", (int)(tpars.heading) );
1694 strcat( &dum[0], &buf[0] );
1697 else if ( strcmp ( tag, "@VD" ) == 0 ) {
1699 if ( tpars.VDir == 1 ) {
1700 char buf[] = "Descend and maintain";
1701 strcat( &dum[0], &buf[0] );
1703 else if ( tpars.VDir == 2 ) {
1704 char buf[] = "Maintain";
1705 strcat( &dum[0], &buf[0] );
1707 else if ( tpars.VDir == 3 ) {
1708 char buf[] = "Climb and maintain";
1709 strcat( &dum[0], &buf[0] );
1713 else if ( strcmp ( tag, "@AL" ) == 0 ) {
1716 sprintf( buf, "%i", (int)(tpars.alt) );
1717 strcat( &dum[0], &buf[0] );
1720 else if ( strcmp ( tag, "@MI" ) == 0 ) {
1722 //sprintf( buf, "%3.1f", tpars.miles );
1723 int dist_miles = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
1724 sprintf(buf, "%i", dist_miles);
1725 strcat( &dum[0], &buf[0] );
1727 else if ( strcmp ( tag, "@FR" ) == 0 ) {
1730 sprintf( buf, "%6.2f", tpars.freq );
1731 strcat( &dum[0], &buf[0] );
1734 else if ( strcmp ( tag, "@RW" ) == 0 ) {
1735 strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
1736 } else if(strcmp(tag, "@CD") == 0) { // @CD = compass direction
1737 double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1738 while(h < 0.0) h += 360.0;
1739 while(h > 360.0) h -= 360.0;
1740 if(h < 22.5 || h > 337.5) {
1741 strcat(&dum[0], "North");
1742 } else if(h < 67.5) {
1743 strcat(&dum[0], "North-East");
1744 } else if(h < 112.5) {
1745 strcat(&dum[0], "East");
1746 } else if(h < 157.5) {
1747 strcat(&dum[0], "South-East");
1748 } else if(h < 202.5) {
1749 strcat(&dum[0], "South");
1750 } else if(h < 247.5) {
1751 strcat(&dum[0], "South-West");
1752 } else if(h < 292.5) {
1753 strcat(&dum[0], "West");
1755 strcat(&dum[0], "North-West");
1758 cout << "Tag " << tag << " not found" << endl;
1761 strcat( &dum[0], &mes[len+3] );
1762 strcpy( &mes[0], &dum[0] );
1766 SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)");
1771 //cout << mes << endl;
1775 if ( mes != "" ) return mes;
1776 else return "No transmission found";
1779 ostream& operator << (ostream& os, tower_traffic_type ttt) {
1781 case(CIRCUIT): return(os << "CIRCUIT");
1782 case(INBOUND): return(os << "INBOUND");
1783 case(OUTBOUND): return(os << "OUTBOUND");
1784 case(TTT_UNKNOWN): return(os << "UNKNOWN");
1785 case(STRAIGHT_IN): return(os << "STRAIGHT_IN");
1787 return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");