From: daveluff Date: Thu, 27 Mar 2003 15:41:09 +0000 (+0000) Subject: Progress towards interactive ATC... X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=7fc214fab368bd007986b3990b4f14360670ea5d;p=flightgear.git Progress towards interactive ATC... --- diff --git a/src/ATC/ATCmgr.cxx b/src/ATC/ATCmgr.cxx index e6a9cb774..a48144417 100644 --- a/src/ATC/ATCmgr.cxx +++ b/src/ATC/ATCmgr.cxx @@ -48,8 +48,10 @@ AirportATC::AirportATC() : ground_freq(0.0), ground_active(false), set_by_AI(false), - numAI(0), - set_by_comm_search(false) { + numAI(0) +{ + set_by_comm[0] = false; + set_by_comm[1] = false; } FGATCMgr::FGATCMgr() { @@ -110,14 +112,14 @@ void FGATCMgr::init() { // Initialise the ATC Dialogs //cout << "Initing Transmissions..." << endl; - SG_LOG(SG_GENERAL, SG_INFO, " ATC Transmissions"); + SG_LOG(SG_ATC, SG_INFO, " ATC Transmissions"); current_transmissionlist = new FGTransmissionList; SGPath p_transmission( globals->get_fg_root() ); p_transmission.append( "ATC/default.transmissions" ); current_transmissionlist->init( p_transmission ); //cout << "Done Transmissions" << endl; - SG_LOG(SG_GENERAL, SG_INFO, " ATC Dialog System"); + SG_LOG(SG_ATC, SG_INFO, " ATC Dialog System"); current_atcdialog = new FGATCDialog; current_atcdialog->Init(); @@ -126,16 +128,6 @@ void FGATCMgr::init() { // DCL - testing //current_atcdialog->add_entry( "EGNX", "Request vectoring for approach", "Request Vectors" ); //current_atcdialog->add_entry( "EGNX", "Mayday, Mayday", "Declare Emergency" ); - /* - ATCData test; - bool ok = current_commlist->FindByCode("KEMT", test, TOWER); - if(ok) { - cout << "KEMT tower frequency from test was " << test.freq << endl; - } else { - cout << "KEMT tower frequency not found :-(" << endl; - } - exit(-1); - */ } void FGATCMgr::update(double dt) { @@ -150,7 +142,7 @@ void FGATCMgr::update(double dt) { } //cout << "Updating " << (*atc_list_itr)->get_ident() << ' ' << (*atc_list_itr)->GetType() << '\n'; //cout << "Freq = " << (*atc_list_itr)->get_freq() << '\n'; - (*atc_list_itr)->Update(); + (*atc_list_itr)->Update(dt * atc_list.size()); //cout << "Done ATC update..." << endl; ++atc_list_itr; } @@ -158,6 +150,7 @@ void FGATCMgr::update(double dt) { // Search the tuned frequencies every now and then - this should be done with the event scheduler static int i = 0; if(i == 7) { + //cout << "About to AreaSearch()" << endl; AreaSearch(); } if(i == 15) { @@ -172,6 +165,7 @@ void FGATCMgr::update(double dt) { ++i; //cout << "comm1 type = " << comm_type[0] << '\n'; + //cout << "Leaving update..." << endl; } @@ -186,26 +180,32 @@ unsigned short int FGATCMgr::GetFrequency(string ident, atc_type tp) { // Register the fact that the AI system wants to activate an airport // Might need more sophistication in this in the future - eg registration by aircraft call-sign. bool FGATCMgr::AIRegisterAirport(string ident) { + SG_LOG(SG_ATC, SG_BULK, "AI registered airport " << ident << " with the ATC system"); if(airport_atc_map.find(ident) != airport_atc_map.end()) { airport_atc_map[ident]->set_by_AI = true; + airport_atc_map[ident]->numAI++; return(true); } else { FGAirport ap; if(dclFindAirportID(ident, &ap)) { + //cout << "ident = " << ident << '\n'; AirportATC *a = new AirportATC; // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! a->lon = ap.longitude; a->lat = ap.latitude; a->elev = ap.elevation; a->atis_freq = GetFrequency(ident, ATIS); + //cout << "ATIS freq = " << a->atis_freq << '\n'; a->atis_active = false; a->tower_freq = GetFrequency(ident, TOWER); + //cout << "Tower freq = " << a->tower_freq << '\n'; a->tower_active = false; a->ground_freq = GetFrequency(ident, GROUND); + //cout << "Ground freq = " << a->ground_freq << '\n'; a->ground_active = false; // TODO - some airports will have a tower/ground frequency but be inactive overnight. a->set_by_AI = true; - a->numAI++; + a->numAI = 1; airport_atc_map[ident] = a; return(true); } @@ -215,29 +215,113 @@ bool FGATCMgr::AIRegisterAirport(string ident) { // Register the fact that the comm radio is tuned to an airport -bool FGATCMgr::CommRegisterAirport(string ident) { // Later we'll differentiate between comm 1 and comm2 - // TODO - implement me! +// Channel is zero based +bool FGATCMgr::CommRegisterAirport(string ident, int chan) { + SG_LOG(SG_ATC, SG_BULK, "Comm channel " << chan << " registered airport " << ident); + if(airport_atc_map.find(ident) != airport_atc_map.end()) { + //cout << "IN MAP - flagging set by comm..." << endl; + if(chan == 0) { + airport_atc_map[ident]->set_by_comm[0] = true; + } else if(chan == 1) { + airport_atc_map[ident]->set_by_comm[1] = true; + } else { + SG_LOG(SG_ATC, SG_ALERT, "COMM CHANNEL NOT 0 OR 1 IN FGATCMGR!"); + // Just register both and accept the fact that this ATC will probably never get removed! + airport_atc_map[ident]->set_by_comm[0] = true; + airport_atc_map[ident]->set_by_comm[1] = true; + } + return(true); + } else { + //cout << "NOT IN MAP - creating new..." << endl; + FGAirport ap; + if(dclFindAirportID(ident, &ap)) { + AirportATC *a = new AirportATC; + // I'm not entirely sure that this AirportATC structure business is actually needed - it just duplicates what we can find out anyway! + a->lon = ap.longitude; + a->lat = ap.latitude; + a->elev = ap.elevation; + a->atis_freq = GetFrequency(ident, ATIS); + a->atis_active = false; + a->tower_freq = GetFrequency(ident, TOWER); + a->tower_active = false; + a->ground_freq = GetFrequency(ident, GROUND); + a->ground_active = false; + // TODO - some airports will have a tower/ground frequency but be inactive overnight. + a->set_by_AI = false; + a->numAI = 0; + if(chan == 0) { + a->set_by_comm[0] = true; + } else if(chan == 1) { + a->set_by_comm[1] = true; + } else { + SG_LOG(SG_ATC, SG_ALERT, "COMM CHANNEL NOT 1 OR 2 IN FGATCMGR!"); + // Just register both and accept the fact that this ATC will probably never get removed! + a->set_by_comm[0] = true; + a->set_by_comm[1] = true; + } + airport_atc_map[ident] = a; + return(true); + } + } return(false); } // Remove from list only if not needed by the AI system or the other comm channel -// TODO - implement me!! +// Note that chan is zero based. void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) { - /*AirportATC a; - if(GetAirportATCDetails((string)id, &a)) { - if(a.set_by_AI) { + SG_LOG(SG_ATC, SG_BULK, "CommRemoveFromList called for airport " << id << " by channel " << chan); + if(airport_atc_map.find((string)id) != airport_atc_map.end()) { + AirportATC* a = airport_atc_map[(string)id]; + //cout << "In CommRemoveFromList, a->ground_freq = " << a->ground_freq << endl; + if(a->set_by_AI) { // Don't remove - a.set_by_comm_search = false; + FGATC* aptr = GetATCPointer((string)id, tp); + switch(chan) { + case 0: + //cout << "chan 1\n"; + a->set_by_comm[0] = false; + if(!a->set_by_comm[1]) { + //cout << "not set by comm2\n"; + if(aptr != NULL) { + //cout << "Got pointer\n"; + aptr->SetNoDisplay(); + //cout << "Setting no display...\n"; + } else { + //cout << "Not got pointer\n"; + } + } + break; + case 1: + a->set_by_comm[1] = false; + if(!a->set_by_comm[0]) { + if(aptr != NULL) { + aptr->SetNoDisplay(); + //cout << "Setting no display...\n"; + } + } + break; + } airport_atc_map[(string)id] = a; return; } else { - // remove + switch(chan) { + case 0: + a->set_by_comm[0] = false; + // Remove only if not also set by the other comm channel + if(!a->set_by_comm[1]) { + RemoveFromList(id, tp); + } + break; + case 1: + a->set_by_comm[1] = false; + if(!a->set_by_comm[0]) { + RemoveFromList(id, tp); + } + break; + } } - }*/ - - // Hack - need to implement this properly - RemoveFromList(id, tp); + } } @@ -255,7 +339,7 @@ void FGATCMgr::RemoveFromList(const char* id, atc_type tp) { //Before removing it stop it transmitting!! //cout << "OBLITERATING FROM LIST!!!\n"; (*atc_list_itr)->SetNoDisplay(); - (*atc_list_itr)->Update(); + (*atc_list_itr)->Update(0.00833); delete (*atc_list_itr); atc_list_itr = atc_list.erase(atc_list_itr); break; @@ -294,6 +378,8 @@ bool FGATCMgr::GetAirportATCDetails(string icao, AirportATC* a) { // Return a pointer to a given sort of ATC at a given airport and activate if necessary // Returns NULL if service doesn't exist - calling function should check for this. +// We really ought to make this private and call it from the CommRegisterAirport / AIRegisterAirport functions +// - at the moment all these GetATC... functions exposed are just too complicated. FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { if(airport_atc_map.find(icao) == airport_atc_map.end()) { return NULL; @@ -303,7 +389,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { //cout << "a->elev = " << a->elev << '\n'; //cout << "a->tower_freq = " << a->tower_freq << '\n'; switch(type) { - case TOWER: + case TOWER: if(a->tower_active) { // Get the pointer from the list return(FindInList(icao.c_str(), type)); @@ -318,16 +404,17 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { t->Init(); return(t); } else { - cout << "ERROR - tower that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found\n"; + SG_LOG(SG_ATC, SG_ALERT, "ERROR - tower that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); } } break; - case APPROACH: + case APPROACH: break; - case ATIS: - SG_LOG(SG_GENERAL, SG_ALERT, "ERROR - ATIS station should not be requested from FGATCMgr::GetATCPointer"); + case ATIS: + SG_LOG(SG_ATC, SG_ALERT, "ERROR - ATIS station should not be requested from FGATCMgr::GetATCPointer"); break; - case GROUND: + case GROUND: + //cout << "IN CASE GROUND" << endl; if(a->ground_active) { // Get the pointer from the list return(FindInList(icao.c_str(), type)); @@ -342,7 +429,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { g->Init(); return(g); } else { - cout << "ERROR - ground control that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found\n"; + SG_LOG(SG_ATC, SG_ALERT, "ERROR - ground control that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found"); } } break; @@ -354,7 +441,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { break; } - SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer"); + SG_LOG(SG_ATC, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer"); return(NULL); } @@ -434,19 +521,33 @@ void FGATCMgr::FreqSearch(int channel) { // This was a switch-case statement but the compiler didn't like the new variable creation with it. if(comm_type[chan] == ATIS) { - FGATIS* a = new FGATIS; - a->SetData(&data); - comm_atc_ptr[chan] = a; - a->SetDisplay(); - //a->set_refname((chan) ? "atis2" : "atis1"); // FIXME - that line is limited to 2 channels - atc_list.push_back(a); + CommRegisterAirport(comm_ident[chan], chan); + FGATC* app = FindInList(comm_ident[chan], TOWER); + if(app != NULL) { + // The station is already in the ATC list + //cout << "In list - flagging SetDisplay..." << endl; + app->SetDisplay(); + } else { + // Generate the station and put in the ATC list + //cout << "Not in list - generating..." << endl; + FGATIS* a = new FGATIS; + a->SetData(&data); + comm_atc_ptr[chan] = a; + a->SetDisplay(); + //a->Init(); + atc_list.push_back(a); + } } else if (comm_type[chan] == TOWER) { + CommRegisterAirport(comm_ident[chan], chan); + //cout << "Done (TOWER)" << endl; FGATC* app = FindInList(comm_ident[chan], TOWER); if(app != NULL) { // The station is already in the ATC list + //cout << "In list - flagging SetDisplay..." << endl; app->SetDisplay(); } else { // Generate the station and put in the ATC list + //cout << "Not in list - generating..." << endl; FGTower* t = new FGTower; t->SetData(&data); comm_atc_ptr[chan] = t; @@ -454,8 +555,26 @@ void FGATCMgr::FreqSearch(int channel) { t->Init(); atc_list.push_back(t); } + } else if (comm_type[chan] == GROUND) { + CommRegisterAirport(comm_ident[chan], chan); + //cout << "Done (GROUND)" << endl; + FGATC* app = FindInList(comm_ident[chan], GROUND); + if(app != NULL) { + // The station is already in the ATC list + app->SetDisplay(); + } else { + // Generate the station and put in the ATC list + FGGround* g = new FGGround; + g->SetData(&data); + comm_atc_ptr[chan] = g; + g->SetDisplay(); + g->Init(); + atc_list.push_back(g); + } } else if (comm_type[chan] == APPROACH) { // We have to be a bit more carefull here since approaches are also searched by area + CommRegisterAirport(comm_ident[chan], chan); + //cout << "Done (APPROACH)" << endl; FGATC* app = FindInList(comm_ident[chan], APPROACH); if(app != NULL) { // The station is already in the ATC list @@ -529,7 +648,7 @@ void FGATCMgr::AreaSearch() { // if approach has no planes left remove it from ATC list if ( np == 0) { (*atc_list_itr)->SetNoDisplay(); - (*atc_list_itr)->Update(); + (*atc_list_itr)->Update(0.00833); delete (*atc_list_itr); atc_list_itr = atc_list.erase(atc_list_itr); break; // the other stations will be checked next time diff --git a/src/ATC/ATCmgr.hxx b/src/ATC/ATCmgr.hxx index be9032128..e7f973499 100644 --- a/src/ATC/ATCmgr.hxx +++ b/src/ATC/ATCmgr.hxx @@ -69,8 +69,7 @@ struct AirportATC { // Flags to ensure the stations don't get wrongly deactivated bool set_by_AI; // true when the AI manager has activated this station unsigned int numAI; // Ref count of the number of AI planes registered - bool set_by_comm_search; // true when the comm_search has activated this station - // Do we need to distingiush comm1 and comm2? + bool set_by_comm[2]; // true when the relevant comm_freq has activated this station }; class FGATCMgr : public FGSubsystem @@ -190,7 +189,7 @@ public: bool AIRegisterAirport(string ident); // Register the fact that the comm radio is tuned to an airport - bool CommRegisterAirport(string ident); // Later we'll differentiate between comm 1 and comm2 + bool CommRegisterAirport(string ident, int chan); private: diff --git a/src/ATC/ground.cxx b/src/ATC/ground.cxx index 6734b8be3..66daabc7e 100644 --- a/src/ATC/ground.cxx +++ b/src/ATC/ground.cxx @@ -29,7 +29,10 @@ #include STL_FSTREAM #include "ground.hxx" +#include "ATCmgr.hxx" #include "ATCutils.hxx" +#include "ATCdisplay.hxx" +#include "AILocalTraffic.hxx" SG_USING_STD(ifstream); SG_USING_STD(cout); @@ -51,11 +54,15 @@ a_path::a_path() { FGGround::FGGround() { display = false; networkLoadOK = false; + ground_traffic.erase(ground_traffic.begin(), ground_traffic.end()); + ground_traffic_itr = ground_traffic.begin(); } FGGround::FGGround(string id) { display = false; networkLoadOK = false; + ground_traffic.erase(ground_traffic.begin(), ground_traffic.end()); + ground_traffic_itr = ground_traffic.begin(); ident = id; } @@ -101,11 +108,11 @@ bool FGGround::LoadNetwork() { string taxiPath = "ATC/KEMT.taxi"; // FIXME - HARDWIRED FOR TESTING path.append(taxiPath); - SG_LOG(SG_GENERAL, SG_INFO, "Trying to read taxiway data for " << ident << "..."); + SG_LOG(SG_ATC, SG_INFO, "Trying to read taxiway data for " << ident << "..."); //cout << "Trying to read taxiway data for " << ident << "..." << endl; fin.open(path.c_str(), ios::in); if(!fin) { - SG_LOG(SG_GENERAL, SG_ALERT, "Unable to open taxiway data input file " << path.c_str()); + SG_LOG(SG_ATC, SG_ALERT, "Unable to open taxiway data input file " << path.c_str()); //cout << "Unable to open taxiway data input file " << path.c_str() << endl; return(false); } @@ -117,7 +124,7 @@ bool FGGround::LoadNetwork() { // Node, arc, or [End]? //cout << "Read in ground network element type = " << buf << endl; if(!strcmp(buf, "[End]")) { // TODO - maybe make this more robust to spelling errors by just looking for '[' - SG_LOG(SG_GENERAL, SG_INFO, "Done reading " << path.c_str() << endl); + SG_LOG(SG_ATC, SG_INFO, "Done reading " << path.c_str() << endl); break; } else if(!strcmp(buf, "N")) { // Node @@ -139,7 +146,7 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "H")) { np->type = HOLD; } else { - SG_LOG(SG_GENERAL, SG_ALERT, "**** ERROR ***** Unknown node type in taxi network...\n"); + SG_LOG(SG_ATC, SG_ALERT, "**** ERROR ***** Unknown node type in taxi network...\n"); delete np; return(false); } @@ -177,7 +184,7 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "T")) { ap->type = TAXIWAY; } else { - SG_LOG(SG_GENERAL, SG_ALERT, "**** ERROR ***** Unknown arc type in taxi network...\n"); + SG_LOG(SG_ATC, SG_ALERT, "**** ERROR ***** Unknown arc type in taxi network...\n"); delete ap; return(false); } @@ -188,7 +195,7 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "N")) { ap->directed = false; } else { - SG_LOG(SG_GENERAL, SG_ALERT, "**** ERROR ***** Unknown arc directed value in taxi network - should be Y/N !!!\n"); + SG_LOG(SG_ATC, SG_ALERT, "**** ERROR ***** Unknown arc directed value in taxi network - should be Y/N !!!\n"); delete ap; return(false); } @@ -240,7 +247,7 @@ bool FGGround::LoadNetwork() { gateCount++; } else { // Something has gone seriously pear-shaped - SG_LOG(SG_GENERAL, SG_ALERT, "********* ERROR - unknown ground network element type... aborting read of " << path.c_str() << '\n'); + SG_LOG(SG_ATC, SG_ALERT, "********* ERROR - unknown ground network element type... aborting read of " << path.c_str() << '\n'); return(false); } @@ -252,15 +259,19 @@ bool FGGround::LoadNetwork() { void FGGround::Init() { display = false; - // For now we'll hardwire the threshold end + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // For now we'll hardwire the threshold end FIXME FIXME FIXME - use actual active rwy Point3D P010(-118.037483, 34.081358, 296 * SG_FEET_TO_METER); double hdg = 25.32; ortho.Init(P010, hdg); + // FIXME TODO FIXME TODO + // TODO FIXME TODO FIXME + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! networkLoadOK = LoadNetwork(); } -void FGGround::Update() { +void FGGround::Update(double dt) { // Each time step, what do we need to do? // We need to go through the list of outstanding requests and acknowedgements // and process at least one of them. @@ -272,7 +283,53 @@ void FGGround::Update() { // Lets take the example of a plane which has just contacted ground // following landing - presumably requesting where to go? // First we need to establish the position of the plane within the logical network. - // Next we need to decide where its going. + // Next we need to decide where its going. + + if(ground_traffic.size()) { + if(ground_traffic_itr == ground_traffic.end()) { + ground_traffic_itr = ground_traffic.begin(); + } + + //Process(*ground_traffic_itr); + GroundRec* g = *ground_traffic_itr; + if(g->taxiRequestOutstanding) { + double responseTime = 10.0; // seconds - this should get more sophisticated at some point + if(g->clearanceCounter > responseTime) { + // DO CLEARANCE + // TODO - move the mechanics of making up the transmission out of the main Update(...) routine. + string trns = ""; + trns += g->plane.callsign; + trns += " taxi holding point runway "; // TODO - add the holding point name + // eg " taxi holding point G2 runway " + //trns += " + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + g->planePtr->RegisterTransmission(1); // cleared to taxi + g->clearanceCounter = 0.0; + g->taxiRequestOutstanding = false; + } else { + g->clearanceCounter += (dt * ground_traffic.size()); + } + } else if(((FGAILocalTraffic*)(g->planePtr))->AtHoldShort()) { // That's a hack - eventually we should monitor actual position + // HACK ALERT - the automatic cast to AILocalTraffic has to go once we have other sorts working!!!!! FIXME TODO + // NOTE - we don't need to do the contact tower bit unless we have separate tower and ground + string trns = g->plane.callsign; + trns += " contact Tower "; + double f = globals->get_ATC_mgr()->GetFrequency(ident, TOWER); + char buf[10]; + sprintf(buf, "%f", f); + trns += buf; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + g->planePtr->RegisterTransmission(2); // contact tower + delete *ground_traffic_itr; + ground_traffic.erase(ground_traffic_itr); + ground_traffic_itr = ground_traffic.begin(); + } + ++ground_traffic_itr; + } } // Return a random gate ID of an unused gate. @@ -347,6 +404,7 @@ node* FGGround::GetThresholdNode(string rwyID) { return NULL; } + // Get a path from a point on a runway to a gate // TODO !! @@ -362,7 +420,7 @@ ground_network_path_type FGGround::GetPath(node* A, node* B) { ground_network_path_type FGGround::GetPath(node* A, string rwyID) { node* b = GetThresholdNode(rwyID); if(b == NULL) { - SG_LOG(SG_GENERAL, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx\n"); + SG_LOG(SG_ATC, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx\n"); ground_network_path_type emptyPath; emptyPath.erase(emptyPath.begin(), emptyPath.end()); return(emptyPath); @@ -370,6 +428,16 @@ ground_network_path_type FGGround::GetPath(node* A, string rwyID) { return GetShortestPath(A, b); } +// Get a path from a node to a runway hold short point +// Bit of a hack this at the moment! +ground_network_path_type FGGround::GetPathToHoldShort(node* A, string rwyID) { + ground_network_path_type path = GetPath(A, rwyID); + path.pop_back(); // That should be the threshold stripped of + path.pop_back(); // and that should be the arc from hold short to threshold + // This isn't robust though - TODO - implement properly! + return(path); +} + // A shortest path algorithm from memory (ie. I can't find the bl&*dy book again!) // I'm sure there must be enchancements that we can make to this, such as biasing the // order in which the nodes are searched out from in favour of those geographically @@ -496,7 +564,7 @@ ground_network_path_type FGGround::GetShortestPath(node* A, node* B) { //cout << "pathsCreated = " << pathsCreated << '\n'; if(pathsCreated > 0) { - SG_LOG(SG_GENERAL, SG_ALERT, "WARNING - Possible memory leak in FGGround::GetShortestPath\n\ + SG_LOG(SG_ATC, SG_ALERT, "WARNING - Possible memory leak in FGGround::GetShortestPath\n\ Please report to flightgear-devel@flightgear.org\n"); } @@ -511,8 +579,32 @@ ground_network_path_type FGGround::GetShortestPath(node* A, node* B) { // Return a list of exits from a given runway // It is up to the calling function to check for non-zero size of returned array before use -node_array_type FGGround::GetExits(int rwyID) { - return(runways[rwyID].exits); +node_array_type FGGround::GetExits(string rwyID) { + // FIXME - get a 07L or similar in here and we're stuffed!!! + return(runways[atoi(rwyID.c_str())].exits); +} + +void FGGround::RequestDeparture(PlaneRec plane, FGAIEntity* requestee) { + // For now we'll just automatically clear all planes to the runway hold. + // This communication needs to be delayed 20 sec or so from receiving the request. + // Even if display=false we still need to start the timer in case display=true when communication starts. + // We also need to bear in mind we also might have other outstanding communications, although for now we'll punt that issue! + // FIXME - sort the above! + + // HACK - assume that anything requesting departure is new for now - FIXME LATER + GroundRec* g = new GroundRec; + g->plane = plane; + g->planePtr = requestee; + g->taxiRequestOutstanding = true; + g->clearanceCounter = 0; + g->cleared = false; + g->incoming = false; + // TODO - need to handle the next 3 as well + //Point3D current_pos; + //node* destination; + //node* last_clearance; + + ground_traffic.push_back(g); } #if 0 diff --git a/src/ATC/ground.hxx b/src/ATC/ground.hxx index 63fc45894..4ec09fca8 100644 --- a/src/ATC/ground.hxx +++ b/src/ATC/ground.hxx @@ -35,7 +35,10 @@ SG_USING_STD(ios); #include #include "ATC.hxx" +//#include "ATCmgr.hxx" #include "ATCProjection.hxx" +#include "AIEntity.hxx" +//#include "AILocalTraffic.hxx" // RunwayDetails - this is a temporary hack SG_USING_STD(map); SG_USING_STD(vector); @@ -140,6 +143,7 @@ struct Rwy { // Eventually we will also want some encoding of real-life preferred runways // This will get us up and running for single runway airports though. }; + typedef vector < Rwy > runway_array_type; typedef runway_array_type::iterator runway_array_iterator; typedef runway_array_type::const_iterator runway_array_const_iterator; @@ -177,28 +181,45 @@ typedef shortest_path_map_type::iterator shortest_path_map_iterator; //////////////////////////////////////////////// // Planes active within the ground network. -// somewhere in the ATC/AI system we are going to have defined something like -// typedef struct plane_rec -// list plane_rec_list_type -/* + // A more specialist plane rec to include ground information -typedef struct ground_rec { - plane_rec plane; - point current_pos; - node destination; - node last_clearance; +struct GroundRec { + FGAIEntity* planePtr; // This might move to the planeRec eventually + + PlaneRec plane; + Point3D current_pos; + node* destination; + node* last_clearance; + bool taxiRequestOutstanding; // Plane has requested taxi and we haven't responded yet + double clearanceCounter; // Hack for communication timing - counter since clearance requested in seconds + bool cleared; // set true when the plane has been cleared to somewhere bool incoming; //true for arrivals, false for departures // status? // Almost certainly need to add more here }; -typedef list ground_rec_list; +typedef list < GroundRec* > ground_rec_list; typedef ground_rec_list::iterator ground_rec_list_itr; typedef ground_rec_list::const_iterator ground_rec_list_const_itr; -*/ + ////////////////////////////////////////////////////////////////////////////////////////// +// Hack +// perhaps we could use an FGRunway instead of this +struct GRunwayDetails { + Point3D threshold_pos; + Point3D end1ortho; // ortho projection end1 (the threshold ATM) + Point3D end2ortho; // ortho projection end2 (the take off end in the current hardwired scheme) + double mag_hdg; + double mag_var; + double hdg; // true runway heading + double length; // In *METERS* + int ID; // 1 -> 36 + string rwyID; + // Do we really need *both* the last two?? +}; + /////////////////////////////////////////////////////////////////////////////// // // FGGround @@ -212,20 +233,18 @@ public: ~FGGround(); void Init(); - void Update(); + void Update(double dt); inline string get_trans_ident() { return trans_ident; } inline atc_type GetType() { return GROUND; } inline void SetDisplay() {display = true;} inline void SetNoDisplay() {display = false;} - // Its possible that NewArrival and NewDeparture should simply be rolled into Request. - // Contact ground control on arrival, assumed to request any gate //void NewArrival(plane_rec plane); // Contact ground control on departure, assumed to request currently active runway. - //void NewDeparture(plane_rec plane); + void RequestDeparture(PlaneRec plane, FGAIEntity* requestee); // Contact ground control when the calling routine doesn't know if arrival // or departure is appropriate. @@ -246,15 +265,20 @@ public: // Runway stuff - this might change in the future. // Get a list of exits from a given runway // It is up to the calling function to check for non-zero size of returned array before use - node_array_type GetExits(int rwyID); + node_array_type GetExits(string rwyID); // Get a path from one node to another ground_network_path_type GetPath(node* A, node* B); // Get a path from a node to a runway threshold ground_network_path_type GetPath(node* A, string rwyID); + + // Get a path from a node to a runway hold short point + // Bit of a hack this at the moment! + ground_network_path_type GetPathToHoldShort(node* A, string rwyID); private: + // Tower stuff // Need a data structure to hold details of the various active planes // Need a data structure to hold details of the logical network @@ -284,9 +308,6 @@ private: // Find the shortest route through the logical network between two points. //FindShortestRoute(point a, point b); - // Project a point in WGS84 lat/lon onto the local gnomonic. - //ConvertWGS84ToXY(sgVec3 wgs84, point xy); - // Assign a gate or parking location to a new arrival //AssignGate(ground_rec &g); @@ -318,8 +339,12 @@ private: // Returns NULL if unsuccessful. node* GetThresholdNode(string rwyID); - // A shortest path algorithm sort of from memory (I can't find the bl&*dy book again!) + // A shortest path algorithm from memory (I can't find the bl&*dy book again!) ground_network_path_type GetShortestPath(node* A, node* B); + + // Planes + ground_rec_list ground_traffic; + ground_rec_list_itr ground_traffic_itr; }; #endif // _FG_GROUND_HXX diff --git a/src/ATC/tower.cxx b/src/ATC/tower.cxx index f5f26e343..0eb98c79a 100644 --- a/src/ATC/tower.cxx +++ b/src/ATC/tower.cxx @@ -19,58 +19,79 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include
+#include +#include +#include #include "tower.hxx" #include "ATCdisplay.hxx" #include "ATCmgr.hxx" +#include "ATCutils.hxx" +#include "commlist.hxx" SG_USING_STD(cout); // TowerPlaneRec TowerPlaneRec::TowerPlaneRec() : -id("UNKNOWN"), clearedToLand(false), -clearedToDepart(false), +clearedToLineUp(false), +clearedToTakeOff(false), +holdShortReported(false), longFinalReported(false), longFinalAcknowledged(false), finalReported(false), -finalAcknowledged(false) +finalAcknowledged(false), +leg(TWR_UNKNOWN), +isUser(false) { + plane.callsign = "UNKNOWN"; } -TowerPlaneRec::TowerPlaneRec(string ID) : +TowerPlaneRec::TowerPlaneRec(PlaneRec p) : clearedToLand(false), -clearedToDepart(false), +clearedToLineUp(false), +clearedToTakeOff(false), +holdShortReported(false), longFinalReported(false), longFinalAcknowledged(false), finalReported(false), -finalAcknowledged(false) +finalAcknowledged(false), +leg(TWR_UNKNOWN), +isUser(false) { - id = ID; + plane = p; } TowerPlaneRec::TowerPlaneRec(Point3D pt) : -id("UNKNOWN"), clearedToLand(false), -clearedToDepart(false), +clearedToLineUp(false), +clearedToTakeOff(false), +holdShortReported(false), longFinalReported(false), longFinalAcknowledged(false), finalReported(false), -finalAcknowledged(false) +finalAcknowledged(false), +leg(TWR_UNKNOWN), +isUser(false) { + plane.callsign = "UNKNOWN"; pos = pt; } -TowerPlaneRec::TowerPlaneRec(string ID, Point3D pt) : +TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) : clearedToLand(false), -clearedToDepart(false), +clearedToLineUp(false), +clearedToTakeOff(false), +holdShortReported(false), longFinalReported(false), longFinalAcknowledged(false), finalReported(false), -finalAcknowledged(false) +finalAcknowledged(false), +leg(TWR_UNKNOWN), +isUser(false) { - id = ID; + plane = p; pos = pt; } @@ -79,6 +100,17 @@ finalAcknowledged(false) FGTower::FGTower() { ATCmgr = globals->get_ATC_mgr(); + + // Init the property nodes - TODO - need to make sure we're getting surface winds. + wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true); + wind_speed_knots = fgGetNode("/environment/wind-speed-kts", true); + + holdListItr = holdList.begin(); + appListItr = appList.begin(); + depListItr = depList.begin(); + rwyListItr = rwyList.begin(); + circuitListItr = circuitList.begin(); + trafficListItr = trafficList.begin(); } FGTower::~FGTower() { @@ -90,19 +122,25 @@ FGTower::~FGTower() { void FGTower::Init() { display = false; + // Pointers to user's position + user_lon_node = fgGetNode("/position/longitude-deg", true); + user_lat_node = fgGetNode("/position/latitude-deg", true); + user_elev_node = fgGetNode("/position/altitude-ft", true); + // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user. // I'll punt the startup issue for now though!!! rwyOccupied = false; // Setup the ground control at this airport AirportATC a; + //cout << "Tower ident = " << ident << '\n'; if(ATCmgr->GetAirportATCDetails(ident, &a)) { if(a.ground_freq) { // Ground control ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND); separateGround = true; if(ground == NULL) { // Something has gone wrong :-( - cout << "ERROR - ground has frequency but can't get ground pointer :-(\n"; + SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-("); ground = new FGGround(ident); separateGround = false; ground->Init(); @@ -125,7 +163,7 @@ void FGTower::Init() { } } } else { - cout << "Unable to find airport details for " << ident << " in FGTower::Init()\n"; + SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()"); // Initialise ground anyway to avoid segfault later ground = new FGGround(ident); separateGround = false; @@ -136,9 +174,28 @@ void FGTower::Init() { ground->SetNoDisplay(); } } + + // Get the airport elevation + aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER; + + DoRwyDetails(); + + // FIXME - this currently assumes use of the active rwy by the user. + rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); + if(rwyOccupied) { + // Assume the user is started at the threshold ready to take-off + TowerPlaneRec* t = new TowerPlaneRec; + t->plane.callsign = "Charlie Foxtrot Sierra"; // C-FGFS !!! - fixme - this is a bit hardwired + t->opType = OUTBOUND; // How do we determine if the user actually wants to do circuits? + t->isUser = true; + t->planePtr = NULL; + t->clearedToTakeOff = true; + rwyList.push_back(t); + } } -void FGTower::Update() { +void FGTower::Update(double dt) { + //cout << "T" << flush; // Each time step, what do we need to do? // We need to go through the list of outstanding requests and acknowedgements // and process at least one of them. @@ -161,6 +218,85 @@ void FGTower::Update() { // sortConficts() !!! + // Do one plane from the hold list + if(holdList.size()) { + //cout << "A" << endl; + //cout << "*holdListItr = " << *holdListItr << endl; + if(holdListItr == holdList.end()) { + holdListItr = holdList.begin(); + } + //cout << "*holdListItr = " << *holdListItr << endl; + //Process(*holdListItr); + TowerPlaneRec* t = *holdListItr; + //cout << "t = " << t << endl; + if(t->holdShortReported) { + //cout << "B" << endl; + double responseTime = 10.0; // seconds - this should get more sophisticated at some point + if(t->clearanceCounter > responseTime) { + //cout << "C" << endl; + if(t->nextOnRwy) { + //cout << "D" << endl; + if(rwyOccupied) { + //cout << "E" << endl; + // Do nothing for now - consider acknowloging hold short eventually + } else { + // Lets Roll !!!! + string trns = t->plane.callsign; + //if(departed plane < some threshold in time away) { + if(0) { // FIXME + trns += " line up"; + t->clearedToLineUp = true; + t->planePtr->RegisterTransmission(3); // cleared to line-up + //} else if(arriving plane < some threshold away) { + } else if(0) { // FIXME + trns += " cleared immediate take-off"; + // TODO - add traffic is... ? + t->clearedToTakeOff = true; + t->planePtr->RegisterTransmission(4); // cleared to take-off - TODO differentiate between immediate and normal take-off + } else { + trns += " cleared for take-off"; + // TODO - add traffic is... ? + t->clearedToTakeOff = true; + t->planePtr->RegisterTransmission(4); // cleared to take-off + } + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + t->holdShortReported = false; + t->clearanceCounter = 0; + rwyList.push_back(t); + rwyOccupied = true; + holdList.erase(holdListItr); + holdListItr = holdList.begin(); + } + } else { + // possibly tell him to hold and what position he is? + } + } else { + t->clearanceCounter += (dt * holdList.size()); + } + } + ++holdListItr; + } + + // 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!! + if(rwyOccupied) { + if(!rwyList.size()) { + rwyOccupied = false; + } else { + rwyListItr = rwyList.begin(); + TowerPlaneRec* t = *rwyListItr; + if(t->isUser) { + bool on_rwy = OnActiveRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); + // TODO - how do we find the position when it's not the user? + if(!on_rwy) { + rwyList.pop_front(); + delete t; + } + } // else TODO figure out what to do when it's not the user + } + } + doCommunication(); if(!separateGround) { @@ -172,25 +308,176 @@ void FGTower::Update() { } else { ground->SetNoDisplay(); } - ground->Update(); + ground->Update(dt); + } + //cout << "R " << flush; +} + + +// Figure out which runways are active. +// For now we'll just be simple and do one active runway - eventually this will get much more complex +// This is a private function - public interface to the results of this is through GetActiveRunway +void FGTower::DoRwyDetails() { + //cout << "GetRwyDetails called" << endl; + + // Based on the airport-id and wind get the active runway + SGPath path( globals->get_fg_root() ); + path.append( "Airports" ); + path.append( "runways.mk4" ); + FGRunways runways( path.c_str() ); + + //wind + double hdg = wind_from_hdg->getDoubleValue(); + double speed = wind_speed_knots->getDoubleValue(); + hdg = (speed == 0.0 ? 270.0 : hdg); + //cout << "Heading = " << hdg << '\n'; + + FGRunway runway; + bool rwyGood = runways.search(ident, int(hdg), &runway); + if(rwyGood) { + activeRwy = runway.rwy_no; + SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy); + + // Get the threshold position + double other_way = runway.heading - 180.0; + while(other_way <= 0.0) { + other_way += 360.0; + } + // move to the +l end/center of the runway + //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n'; + Point3D origin = Point3D(runway.lon, runway.lat, aptElev); + Point3D ref = origin; + double tshlon, tshlat, tshr; + double tolon, tolat, tor; + rwy.length = runway.length * SG_FEET_TO_METER; + geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, + rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr ); + geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway.heading, + rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor ); + // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user. + // now copy what we need out of runway into rwy + rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev); + Point3D takeoff_end = Point3D(tolon, tolat, aptElev); + //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n'; + //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n'; + rwy.hdg = runway.heading; + // Set the projection for the local area based on this active runway + ortho.Init(rwy.threshold_pos, rwy.hdg); + rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero + rwy.end2ortho = ortho.ConvertToLocal(takeoff_end); + } else { + SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGTower!!"); + activeRwy = "NN"; + } +} + + +// Figure out if a given position lies on the active runway +// Might have to change when we consider more than one active rwy. +bool FGTower::OnActiveRunway(Point3D pt) { + SGPath path( globals->get_fg_root() ); + path.append( "Airports" ); + path.append( "runways.mk4" ); + FGRunways runways( path.c_str() ); + + FGRunway runway; + bool rwyGood = runways.search(ident, activeRwy, &runway); + if(!rwyGood) { + SG_LOG(SG_ATC, SG_WARN, "Unable to find runway " << activeRwy << " for airport ID " << ident << " in FGTower"); + } + return(OnRunway(pt, &runway) ? true : false); +} + + +// Figure out if a given position lies on any runway or not +bool FGTower::OnAnyRunway(Point3D pt) { + ATCData ad; + double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 10.0); + if(dist < 0.0) { + return(false); } + // Based on the airport-id, go through all the runways and check for a point in them + SGPath spath( globals->get_fg_root() ); + spath.append( "Airports" ); + spath.append( "runways.mk4" ); + FGRunways runways( spath.c_str() ); + + // TODO - do we actually need to search for the airport - surely we already know our ident and + // can just search runways of our airport??? + //cout << "Airport ident is " << ad.ident << '\n'; + FGRunway runway; + bool rwyGood = runways.search(ad.ident, &runway); + if(!rwyGood) { + SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower"); + } + bool on = false; + while(runway.id == ad.ident) { + on = OnRunway(pt, &runway); + //cout << "Runway " << runway.rwy_no << ": On = " << (on ? "true\n" : "false\n"); + if(on) return(true); + runways.next(&runway); + } + return(on); } + // Calculate the eta of each plane to the threshold. // For ground traffic this is the fastest they can get there. // For air traffic this is the middle approximation. void FGTower::doThresholdETACalc() { // For now we'll be very crude and hardwire expected speeds to C172-like values - double app_ias = 100.0; // Speed during straight-in approach - double circuit_ias = 80.0; // Speed around circuit - double final_ias = 70.0; // Speed during final approach + // The speeds below are specified in knots IAS and then converted to m/s + double app_ias = 100.0 * 0.514444; // Speed during straight-in approach + double circuit_ias = 80.0 * 0.514444; // Speed around circuit + double final_ias = 70.0 * 0.514444; // Speed during final approach tower_plane_rec_list_iterator twrItr; + // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes + // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) { + TowerPlaneRec* tpr = *twrItr; + Point3D op = ortho.ConvertToLocal(tpr->pos); + double dist_out_m = op.y(); + 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 + //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n'; + if(tpr->opType == CIRCUIT) { + // It's complicated - depends on if base leg is delayed or not + if(tpr->leg == TWR_LANDING_ROLL) { + tpr->eta = 0; + } else if(tpr->leg == TWR_FINAL) { + tpr->eta = fabs(dist_out_m) / final_ias; + } else if(tpr->leg == TWR_BASE) { + tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias); + } else { + // Need to calculate where base leg is likely to be + // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!! + // 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 + double nominal_base_dist_out_m = -1000; + double current_base_dist_out_m = nominal_base_dist_out_m; + double nominal_dist_across_m = 1000; // Hardwired value from AILocalTraffic + double nominal_cross_dist_out_m = 1000; // Bit of a guess - AI plane turns to crosswind at 600ft agl. + tpr->eta = fabs(current_base_dist_out_m) / final_ias; // final + if(tpr->leg == TWR_DOWNWIND) { + tpr->eta += dist_across_m / circuit_ias; + tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias; + } else if(tpr->leg == TWR_CROSSWIND) { + tpr->eta += nominal_dist_across_m / circuit_ias; // should we use the dist across of the previous plane if there is previous still on downwind? + tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias; + tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias; + } else { + // We've only just started - why not use a generic estimate? + } + } + } else if((tpr->opType == INBOUND) || (tpr->opType == STRAIGHT_IN)) { + // It's simpler! + } else { + // Must be outbound - ignore it! + } + //cout << "ETA = " << tpr->eta << '\n'; } - } + bool FGTower::doThresholdUseOrder() { return(true); @@ -198,13 +485,31 @@ bool FGTower::doThresholdUseOrder() { void FGTower::doCommunication() { } - + +void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIEntity* requestee, tower_traffic_type operation) { + // HACK - assume that anything contacting at hold short is new for now - FIXME LATER + TowerPlaneRec* t = new TowerPlaneRec; + t->plane = plane; + t->planePtr = requestee; + t->holdShortReported = true; + t->clearanceCounter = 0; + t->clearedToLineUp = false; + t->clearedToTakeOff = false; + t->opType = operation; + + // HACK ALERT - THIS IS HARDWIRED FOR TESTING - FIXME TODO ETC + t->nextOnRwy = true; + + //cout << "t = " << t << '\n'; + + holdList.push_back(t); +} void FGTower::RequestLandingClearance(string ID) { - cout << "Request Landing Clearance called...\n"; + //cout << "Request Landing Clearance called...\n"; } void FGTower::RequestDepartureClearance(string ID) { - cout << "Request Departure Clearance called...\n"; + //cout << "Request Departure Clearance called...\n"; } //void FGTower::ReportFinal(string ID); //void FGTower::ReportLongFinal(string ID); @@ -213,5 +518,5 @@ void FGTower::RequestDepartureClearance(string ID) { //void FGTower::ReportInnerMarker(string ID); //void FGTower::ReportGoingAround(string ID); void FGTower::ReportRunwayVacated(string ID) { - cout << "Report Runway Vacated Called...\n"; + //cout << "Report Runway Vacated Called...\n"; } diff --git a/src/ATC/tower.hxx b/src/ATC/tower.hxx index 430604e8f..25e0ab91a 100644 --- a/src/ATC/tower.hxx +++ b/src/ATC/tower.hxx @@ -24,8 +24,9 @@ #include #include #include -#include +//#include #include +//#include #include STL_IOSTREAM #include STL_STRING @@ -36,6 +37,7 @@ SG_USING_STD(ios); #include "ATC.hxx" //#include "ATCmgr.hxx" #include "ground.hxx" +#include "ATCProjection.hxx" //DCL - a complete guess for now. #define FG_TOWER_DEFAULT_RANGE 30 @@ -48,33 +50,68 @@ enum tower_traffic_type { // Umm - what's the difference between INBOUND and STRAIGHT_IN ? }; +// Much simplified compared to AILocalTraffic +enum TwrPatternLeg { + TWR_LANDING_ROLL, + TWR_FINAL, + TWR_BASE, + TWR_DOWNWIND, + TWR_CROSSWIND, + TWR_CLIMBOUT, + TWR_TAKEOFF_ROLL, + TWR_UNKNOWN +}; + +// perhaps we could use an FGRunway instead of this +struct RunwayDetails { + Point3D threshold_pos; + Point3D end1ortho; // ortho projection end1 (the threshold ATM) + Point3D end2ortho; // ortho projection end2 (the take off end in the current hardwired scheme) + //double mag_hdg; + //double mag_var; + double hdg; // true runway heading + double length; // In *METERS* + string rwyID; +}; + // Structure for holding details of a plane under tower control. // Not fixed yet - may include more stuff later. class TowerPlaneRec { - public: +public: TowerPlaneRec(); - TowerPlaneRec(string ID); + TowerPlaneRec(PlaneRec p); TowerPlaneRec(Point3D pt); - TowerPlaneRec(string ID, Point3D pt); + TowerPlaneRec(PlaneRec p, Point3D pt); + + FGAIEntity* planePtr; // This might move to the planeRec eventually + PlaneRec plane; - string id; Point3D pos; - double eta; // minutes - double dist_out; // miles from theshold + double eta; // seconds + double dist_out; // meters from theshold bool clearedToLand; - bool clearedToDepart; + bool clearedToLineUp; + bool clearedToTakeOff; // ought to add time cleared to depart so we can nag if necessary + bool holdShortReported; bool longFinalReported; bool longFinalAcknowledged; bool finalReported; bool finalAcknowledged; - bool onRwy; - // enum type - light, medium, heavy etc - we need someway of approximating the aircraft type and performance. + bool onRwy; // is physically on the runway + bool nextOnRwy; // currently projected by tower to be the next on the runway + + double clearanceCounter; // Hack for communication timing - counter since clearance requested in seconds // Type of operation the plane is doing tower_traffic_type opType; + + // Whereabouts in circuit if doing circuits + TwrPatternLeg leg; + + bool isUser; // true if this plane is the user }; @@ -92,7 +129,7 @@ public: void Init(); - void Update(); + void Update(double dt); void RequestLandingClearance(string ID); void RequestDepartureClearance(string ID); @@ -103,18 +140,34 @@ public: void ReportInnerMarker(string ID); void ReportGoingAround(string ID); void ReportRunwayVacated(string ID); + void ReportReadyForDeparture(string ID); + + // Contact tower when at a hold short for departure + void ContactAtHoldShort(PlaneRec plane, FGAIEntity* requestee, tower_traffic_type operation); - inline void SetDisplay() {display = true;} - inline void SetNoDisplay() {display = false;} + // Public interface to the active runway - this will get more complex + // in the future and consider multi-runway use, airplane weight etc. + inline string GetActiveRunway() { return activeRwy; } + inline RunwayDetails* GetActiveRunwayDetails() { return &rwy; } + + inline void SetDisplay() { display = true; } + inline void SetNoDisplay() { display = false; } inline string get_trans_ident() { return trans_ident; } inline atc_type GetType() { return TOWER; } - inline FGGround* GetGroundPtr() {return ground; } + inline FGGround* GetGroundPtr() { return ground; } private: FGATCMgr* ATCmgr; // This is purely for synactic convienience to avoid writing globals->get_ATC_mgr()-> all through the code! + + // Figure out if a given position lies on the active runway + // Might have to change when we consider more than one active rwy. + bool OnActiveRunway(Point3D pt); + + // Figure out if a given position lies on a runway or not + bool OnAnyRunway(Point3D pt); // Calculate the eta of each plane to the threshold. // For ground traffic this is the fastest they can get there. @@ -133,29 +186,51 @@ private: bool display; // Flag to indicate whether we should be outputting to the ATC display. bool displaying; // Flag to indicate whether we are outputting to the ATC display. - bool rwyOccupied; // Active runway occupied flag. For now we'll disregard multiple runway and land-and-hold-short operations + // environment - need to make sure we're getting the surface winds and not winds aloft. + SGPropertyNode* wind_from_hdg; //degrees + SGPropertyNode* wind_speed_knots; //knots + + double aptElev; // Airport elevation + string activeRwy; // Active runway number - For now we'll disregard multiple / alternate runway operation. + RunwayDetails rwy; // Assumed to be the active one for now. + bool rwyOccupied; // Active runway occupied flag. For now we'll disregard land-and-hold-short operations. + FGATCAlignedProjection ortho; // Orthogonal mapping of the local area with the active runway threshold at the origin + + // Figure out which runways are active. + // For now we'll just be simple and do one active runway - eventually this will get much more complex + // This is a private function - public interface to the results of this is through GetActiveRunway + void DoRwyDetails(); // Need a data structure to hold details of the various active planes // or possibly another data structure with the positions of the inactive planes. // Need a data structure to hold outstanding communications from aircraft. -/* + // Linked-list of planes on approach ordered with nearest first (timewise). // Includes planes that have landed but not yet vacated runway. // Somewhat analagous to the paper strips used (used to be used?) in real life. + // Doesn't include planes in circuit until they turn onto base/final? tower_plane_rec_list_type appList; + tower_plane_rec_list_iterator appListItr; - // List of departed planes + // List of departed planes (planes doing circuits go into circuitList not depList after departure) tower_plane_rec_list_type depList; + tower_plane_rec_list_iterator depListItr; + + // List of planes in the circuit (ordered by nearest to landing first) + tower_plane_rec_list_type circuitList; + tower_plane_rec_list_iterator circuitListItr; // List of planes waiting to depart tower_plane_rec_list_type holdList; - + tower_plane_rec_list_iterator holdListItr; + // List of planes on rwy tower_plane_rec_list_type rwyList; -*/ + tower_plane_rec_list_iterator rwyListItr; - // Linked list of all planes due to use a given rwy arranged in projected order of rwy use + // List of all planes due to use a given rwy arranged in projected order of rwy use tower_plane_rec_list_type trafficList; // TODO - needs to be expandable to more than one rwy + tower_plane_rec_list_iterator trafficListItr; // Ground can be separate or handled by tower in real life. // In the program we will always use a separate FGGround class, but we need to know @@ -163,11 +238,15 @@ private: bool separateGround; // true if ground control is separate FGGround* ground; // The ground control associated with this airport. - // for failure modeling string trans_ident; // transmitted ident bool tower_failed; // tower failed? + // Pointers to current users position + SGPropertyNode* user_lon_node; + SGPropertyNode* user_lat_node; + SGPropertyNode* user_elev_node; + friend istream& operator>> ( istream&, FGTower& ); };