X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FATC%2Fground.cxx;h=3e2e0f68b95268f04942e872575189f7d27bf943;hb=2bc09e5268284b4d7009fa66930bcfc937400f3b;hp=f713a69e71b758112c06dc0c7080c3dba708a08c;hpb=a1d4e79127aeecbd6292ca2951c927ec0bc47bf4;p=flightgear.git diff --git a/src/ATC/ground.cxx b/src/ATC/ground.cxx index f713a69e7..3e2e0f68b 100644 --- a/src/ATC/ground.cxx +++ b/src/ATC/ground.cxx @@ -29,13 +29,56 @@ #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); +node::node() { +} + +node::~node() { + for(unsigned int i=0; i < arcs.size(); ++i) { + delete arcs[i]; + } +} + +// Make sure that a_path.cost += distance is safe from the moment it's created. +a_path::a_path() { + cost = 0; +} + FGGround::FGGround() { - display = false; + ATCmgr = globals->get_ATC_mgr(); + _type = GROUND; + networkLoadOK = false; + ground_traffic.erase(ground_traffic.begin(), ground_traffic.end()); + ground_traffic_itr = ground_traffic.begin(); + + // 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-kt", true); + + // TODO - get the actual airport elevation + aptElev = 0.0; +} + +FGGround::FGGround(string id) { + ATCmgr = globals->get_ATC_mgr(); networkLoadOK = false; + ground_traffic.erase(ground_traffic.begin(), ground_traffic.end()); + ground_traffic_itr = ground_traffic.begin(); + ident = id; + + // 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-kt", true); + + // TODO - get the actual airport elevation + aptElev = 0.0; } FGGround::~FGGround() { @@ -63,22 +106,28 @@ void FGGround::ParseRwyExits(node* np, char* es) { // Return true if successfull. // TODO - currently the file is assumed to reside in the base/ATC directory. // This might change to something more thought out in the future. +// NOTE - currently it is assumed that all nodes are loaded before any arcs. +// It won't work ATM if this doesn't hold true. bool FGGround::LoadNetwork() { node* np; arc* ap; Gate* gp; + int gateCount = 0; // This is used to allocate gateID's from zero upwards + // This may well change in the future - probably to reading in the real-world + // gate numbers from file. + ifstream fin; SGPath path = globals->get_fg_root(); //string taxiPath = "ATC/" + ident + ".taxi"; 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); } @@ -90,7 +139,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 @@ -112,28 +161,29 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "H")) { np->type = HOLD; } else { - cout << "**** 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); } fin >> buf; // rwy exit information - gets parsed later - FRAGILE - will break if buf is reused. // Now the name + fin >> ch; // strip the leading " off np->name = ""; while(1) { fin.unsetf(ios::skipws); fin >> ch; - np->name += ch; if((ch == '"') || (ch == 0x0A)) { break; } // we shouldn't need the 0x0A but it makes a nice safely in case someone leaves off the " + np->name += ch; } fin.setf(ios::skipws); network.push_back(np); // FIXME - fragile - replies on buf not getting modified from exits read to here // see if we also need to push it onto the runway exit list - cout << "strlen(buf) = " << strlen(buf) << endl; + //cout << "strlen(buf) = " << strlen(buf) << endl; if(strlen(buf) > 2) { - cout << "Calling ParseRwyExits for " << buf << endl; + //cout << "Calling ParseRwyExits for " << buf << endl; ParseRwyExits(np, buf); } } else if(!strcmp(buf, "A")) { @@ -149,7 +199,7 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "T")) { ap->type = TAXIWAY; } else { - cout << "**** 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); } @@ -160,7 +210,7 @@ bool FGGround::LoadNetwork() { } else if(!strcmp(buf, "N")) { ap->directed = false; } else { - cout << "**** 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); } @@ -175,6 +225,8 @@ bool FGGround::LoadNetwork() { } // we shouldn't need the 0x0A but it makes a nice safely in case someone leaves off the " } fin.setf(ios::skipws); + ap->distance = (int)dclGetHorizontalSeparation(network[ap->n1]->pos, network[ap->n2]->pos); + //cout << "Distance = " << ap->distance << '\n'; network[ap->n1]->arcs.push_back(ap); network[ap->n2]->arcs.push_back(ap); } else if(!strcmp(buf, "G")) { @@ -203,10 +255,14 @@ bool FGGround::LoadNetwork() { } // we shouldn't need the 0x0A but it makes a nice safely in case someone leaves off the " } fin.setf(ios::skipws); + gp->id = gateCount; // Warning - this will likely change in the future. + gp->used = false; network.push_back(gp); + gates[gateCount] = gp; + gateCount++; } else { // Something has gone seriously pear-shaped - cout << "********* 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); } @@ -216,17 +272,19 @@ bool FGGround::LoadNetwork() { } void FGGround::Init() { - display = false; - - // For now we'll hardwire the threshold end - Point3D P010(-118.037483, 34.081358, 296 * SG_FEET_TO_METER); - double hdg = 25.32; - ortho.Init(P010, hdg); + untowered = false; + // Figure out which is the active runway - TODO - it would be better to have ground call tower + // for runway operation details, but at the moment we can't guarantee that tower control at a given airport + // will be initialised before ground so we can't do that. + DoRwyDetails(); + //cout << "In FGGround::Init, active rwy is " << activeRwy << '\n'; + ortho.Init(rwy.threshold_pos, rwy.hdg); + 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. @@ -238,119 +296,360 @@ 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 += ConvertRwyNumToSpokenString(activeRwy); + 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) / 100.0; + char buf[10]; + sprintf(buf, "%.2f", 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; + } + + // Call the base class update for the response time handling. + FGATC::Update(dt); } -// FIXME - at the moment this assumes there is at least one gate and crashes if none -// FIXME - In fact, at the moment this routine doesn't work at all and hence is munged to always return Gate 1 !!!! +// 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 +// Copied from FGTower - TODO - it would be better to implement this just once, and have ground call tower +// for runway operation details, but at the moment we can't guarantee that tower control at a given airport +// will be initialised before ground so we can't do that. +void FGGround::DoRwyDetails() { + //cout << "GetRwyDetails called" << endl; + + // Based on the airport-id and wind get the active runway + + //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 = globals->get_runways()->search(ident, int(hdg), &runway); + if(rwyGood) { + activeRwy = runway.rwy_no; + rwy.rwyID = runway.rwy_no; + SG_LOG(SG_ATC, SG_INFO, "In FGGround, 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"; + } +} + +// Return a random gate ID of an unused gate. +// Two error values may be returned and must be checked for by the calling function: +// -2 signifies that no gates exist at this airport. +// -1 signifies that all gates are currently full. int FGGround::GetRandomGateID() { - //cout << "GetRandomGateID called" << endl; - return(1); + // Check that this airport actually has some gates!! + if(!gates.size()) { + return(-2); + } gate_vec_type gateVec; - //gate_vec_iterator gateVecItr; int num = 0; int thenum; int ID; gatesItr = gates.begin(); while(gatesItr != gates.end()) { - if(gatesItr->second.used == false) { + if((gatesItr->second)->used == false) { gateVec.push_back(gatesItr->second); num++; } ++gatesItr; } + + // Check that there are some unused gates! + if(!gateVec.size()) { + return(-1); + } // Randomly select one from the list + sg_srandom_time(); thenum = (int)(sg_random() * gateVec.size()); - ID = gateVec[thenum].id; - //cout << "Returning gate ID " << ID << " from GetRandomGateID" << endl; + ID = gateVec[thenum]->id; + return(ID); } -// Return a pointer to a gate node based on the gate ID -Gate* FGGround::GetGateNode(int gateID) { - //TODO - ought to add some sanity checking here - ie does a gate of this ID exist?! - return(&(gates[gateID])); +// Return a pointer to an unused gate node +Gate* FGGround::GetGateNode() { + int id = GetRandomGateID(); + if(id < 0) { + return(NULL); + } else { + return(gates[id]); + } } + +node* FGGround::GetHoldShortNode(string rwyID) { + return(NULL); // TODO - either implement me or remove me!!! +} + + +// WARNING - This is hardwired to my prototype logical network format +// and will almost certainly change when Bernie's stuff comes on-line. +// Returns NULL if it can't find a valid node. +node* FGGround::GetThresholdNode(string rwyID) { + // For now go through all the nodes and parse their names + // Maybe in the future we'll map threshold nodes by ID + //cout << "Size of network is " << network.size() << '\n'; + for(unsigned int i=0; ipath.push_back(A); + pathPtr->cost = 0; + pathMap[A->nodeID] = pathPtr; + bool solution_found = false; // Flag to indicate that at least one candidate path has been found + int solution_cost = -1; // Cost of current best cost solution. -1 indicates no solution found yet. + a_path solution_path; + + node* nPtr; // nPtr is used to point to the node we are currently working with + + while(nodesLeft.size()) { + //cout << "\n*****nodesLeft*****\n"; + //for(unsigned int i=0; inodeID << '\n'; + //} + //cout << "*******************\n\n"; + nPtr = *nodesLeft.begin(); // Thought - definate optimization possibilities here in the choice of which nodes we process first. + nodesLeft.erase(nodesLeft.begin()); + //cout << "Walking out from node " << nPtr->nodeID << '\n'; + for(unsigned int i=0; iarcs.size(); ++i) { + //cout << "ARC TO " << ((nPtr->arcs[i]->n1 == nPtr->nodeID) ? nPtr->arcs[i]->n2 : nPtr->arcs[i]->n1) << '\n'; } - // Then push back the start of taxiway node - // Then push back the taxiway arc - arcItr = A->arcs.begin(); - found = false; - while(arcItr != A->arcs.end()) { - if(arcItr->type == TAXIWAY) { // FIXME - OOPS - two taxiways go off this node - // How are we going to differentiate, apart from one called Alpha. - // I suppose eventually the traversal algorithms will select. - path.push_back(&(*arcItr)); - found = true; - break; - } + if((solution_found) && (solution_cost <= pathMap[nPtr->nodeID]->cost)) { + // Do nothing - we've already found a solution and this partial path is already more expensive + } else { + // This path could still be better than the current solution - check it out + for(unsigned int i=0; i<(nPtr->arcs.size()); i++) { + // Map the new path against the end node, ie. *not* the one we just started with. + unsigned int end_nodeID = ((nPtr->arcs[i]->n1 == nPtr->nodeID) ? nPtr->arcs[i]->n2 : nPtr->arcs[i]->n1); + //cout << "end_nodeID = " << end_nodeID << '\n'; + //cout << "pathMap size is " << pathMap.size() << '\n'; + if(end_nodeID == nPtr->nodeID) { + //cout << "Circular arc!\n"; + // Then its a circular arc - don't bother!! + //nPtr->arcs.erase(nPtr->arcs.begin() + i); + } else { + // see if the end node is already in the map or not + if(pathMap.find(end_nodeID) == pathMap.end()) { + //cout << "Not in the map" << endl;; + // Not in the map - easy! + pathPtr = new a_path; + pathsCreated++; + *pathPtr = *pathMap[nPtr->nodeID]; // *copy* the path + pathPtr->path.push_back(nPtr->arcs[i]); + pathPtr->path.push_back(network[end_nodeID]); + pathPtr->cost += nPtr->arcs[i]->distance; + pathMap[end_nodeID] = pathPtr; + nodesLeft.push_back(network[end_nodeID]); // By definition this can't be in the list already, or + // it would also have been in the map and hence OR'd with this one. + if(end_nodeID == B->nodeID) { + //cout << "Solution found!!!" << endl; + // Since this node wasn't in the map this is by definition the first solution + solution_cost = pathPtr->cost; + solution_path = *pathPtr; + solution_found = true; + } + } else { + //cout << "Already in the map" << endl; + // In the map - not so easy - need to get rid of an arc from the higher cost one. + //cout << "Current cost of node " << end_nodeID << " is " << pathMap[end_nodeID]->cost << endl; + int newCost = pathMap[nPtr->nodeID]->cost + nPtr->arcs[i]->distance; + //cout << "New cost is of node " << nPtr->nodeID << " is " << newCost << endl; + if(newCost >= pathMap[end_nodeID]->cost) { + // No need to do anything. + //cout << "Not doing anything!" << endl; + } else { + delete pathMap[end_nodeID]; + pathsCreated--; + + pathPtr = new a_path; + pathsCreated++; + *pathPtr = *pathMap[nPtr->nodeID]; // *copy* the path + pathPtr->path.push_back(nPtr->arcs[i]); + pathPtr->path.push_back(network[end_nodeID]); + pathPtr->cost += nPtr->arcs[i]->distance; + pathMap[end_nodeID] = pathPtr; + + // We need to add this node to the list-to-do again to force a recalculation + // onwards from this node with the new lower cost to node cost. + nodesLeft.push_back(network[end_nodeID]); + + if(end_nodeID == B->nodeID) { + //cout << "Solution found!!!" << endl; + // Need to check if there is a previous better solution + if((solution_cost < 0) || (pathPtr->cost < solution_cost)) { + solution_cost = pathPtr->cost; + solution_path = *pathPtr; + solution_found = true; + } + } + } + } + } + } } - if(found == false) { - //cout << "AI/ATC SUBSYSTEM ERROR - no taxiway from runway exit in airport.cxx" << endl; + } + + // delete all the paths before returning + shortest_path_map_iterator spItr = pathMap.begin(); + while(spItr != pathMap.end()) { + if(spItr->second != NULL) { + delete spItr->second; + --pathsCreated; } - // Then push back the junction node - // Planes always face one way in the parking, so depending on which parking exit we have either take it or push back another taxiway node - // Repeat if necessary - // Then push back the gate B - path.push_back(B); - } else { - //return an outward path - } - - // WARNING TODO FIXME - this is VERY FRAGILE - eg taxi to apron!!! but should be enough to - // see an AI plane physically taxi. -#endif // 0 + ++spItr; + } - return(path); -}; + //cout << "pathsCreated = " << pathsCreated << '\n'; + if(pathsCreated > 0) { + SG_LOG(SG_ATC, SG_ALERT, "WARNING - Possible memory leak in FGGround::GetShortestPath\n\ + Please report to flightgear-devel@flightgear.org\n"); + } + + //cout << (solution_found ? "Result: solution found\n" : "Result: no solution found\n"); + return(solution_path.path); // TODO - we really ought to have a fallback position incase a solution isn't found. +} + // Randomly or otherwise populate some of the gates with parked planes @@ -358,8 +657,32 @@ ground_network_path_type FGGround::GetPath(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 @@ -403,3 +726,4 @@ void FGGround::AssignGate(ground_rec &g) { // In the long run the logic of which gate or area to send the plane to could be somewhat non-trivial. } #endif //0 +