1 // FGAILocalTraffic - AIEntity derived class with enough logic to
2 // fly and interact with the traffic pattern.
4 // Written by David Luff, started March 2002.
6 // Copyright (C) 2002 David C. Luff - david.luff@nottingham.ac.uk
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 /*==========================================================
26 Should get pattern direction from tower.
28 Need to continually monitor and adjust deviation from glideslope
29 during descent to avoid occasionally landing short or long.
31 ============================================================*/
37 #include <Airports/runways.hxx>
38 #include <Main/globals.hxx>
39 #include <Main/viewer.hxx>
40 #include <Scenery/scenery.hxx>
41 #include <Scenery/tilemgr.hxx>
42 #include <simgear/math/SGMath.hxx>
43 #include <simgear/misc/sg_path.hxx>
50 #include "AILocalTraffic.hxx"
51 #include "ATCutils.hxx"
54 FGAILocalTraffic::FGAILocalTraffic() {
55 ATC = globals->get_ATC_mgr();
57 // TODO - unhardwire this
58 plane.type = GA_SINGLE;
64 //Hardwire initialisation for now - a lot of this should be read in from config eventually
66 best_rate_of_climb_speed = 70.0;
68 //nominal_climb_speed;
70 //nominal_circuit_speed;
73 nominal_descent_rate = 500.0;
74 nominal_final_speed = 65.0;
75 //nominal_approach_speed;
76 //stall_speed_landing_config;
77 nominalTaxiSpeed = 7.5;
79 wheelOffset = 1.45; // Warning - hardwired to the C172 - we need to read this in from file.
81 // Init the property nodes
82 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
83 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
86 taxiRequestPending = false;
87 taxiRequestCleared = false;
89 clearedToLineUp = false;
90 clearedToTakeOff = false;
91 _clearedToLand = false;
92 reportReadyForDeparture = false;
94 contactGround = false;
99 targetDescentRate = 0.0;
101 goAroundCalled = false;
113 FGAILocalTraffic::~FGAILocalTraffic() {
116 void FGAILocalTraffic::GetAirportDetails(const string& id) {
118 if(ATC->GetAirportATCDetails(airportID, &a)) {
119 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
120 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
122 // Something has gone wrong - abort or carry on with un-towered operation?
123 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
129 ground = tower->GetGroundPtr();
131 // Something has gone wrong :-(
132 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
137 // TODO - Check CTAF, unicom etc
140 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
143 // Get the airport elevation
144 aptElev = fgGetAirportElev(airportID.c_str());
145 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
146 // WARNING - we use this elev for the whole airport - some assumptions in the code
147 // might fall down with very slopey airports.
150 // Get details of the active runway
151 // It is assumed that by the time this is called the tower control and airport code will have been set up.
152 void FGAILocalTraffic::GetRwyDetails(const string& id) {
153 //cout << "GetRwyDetails called" << endl;
155 const FGAirport* apt = fgFindAirportID(id);
157 FGRunway* runway(apt->getActiveRunwayForUsage());
159 double hdg = runway->headingDeg();
160 double other_way = hdg - 180.0;
161 while(other_way <= 0.0) {
165 // move to the +l end/center of the runway
166 //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
167 double tshlon, tshlat, tshr;
168 double tolon, tolat, tor;
169 rwy.length = runway->lengthM();
170 rwy.width = runway->widthM();
171 geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way,
172 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
173 geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), hdg,
174 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
175 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
176 // now copy what we need out of runway into rwy
177 rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev);
178 SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev);
179 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
180 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
182 // Set the projection for the local area
183 //cout << "Initing ortho for airport " << id << '\n';
184 ortho.Init(rwy.threshold_pos, rwy.hdg);
185 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
186 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
191 There are two possible scenarios during initialisation:
192 The first is that the user is flying towards the airport, and hence the traffic
193 could be initialised anywhere, as long as the AI planes are consistent with
195 The second is that the user has started the sim at or close to the airport, and
196 hence the traffic must be initialised with respect to the user as well as each other.
197 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
198 sufficient initialisation functionality within the plane classes to allow the manager
199 to initially position them where and how required.
201 bool FGAILocalTraffic::Init(const string& callsign, const string& ICAO, OperatingState initialState, PatternLeg initialLeg) {
202 //cout << "FGAILocalTraffic.Init(...) called" << endl;
205 plane.callsign = callsign;
207 if(initialState == EN_ROUTE) return(true);
209 // Get the ATC pointers and airport elev
210 GetAirportDetails(airportID);
212 // Get the active runway details (and copy them into rwy)
213 GetRwyDetails(airportID);
214 //cout << "Runway is " << rwy.rwyID << '\n';
216 // FIXME TODO - pattern direction is still hardwired
217 patternDirection = -1; // Left
218 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
219 if(rwy.rwyID.size() == 3) {
220 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
224 if((initialState == PARKED) || (initialState == TAXIING)) {
225 freq = (double)ground->get_freq() / 100.0;
227 freq = (double)tower->get_freq() / 100.0;
231 // TODO - find the proper freq if CTAF or unicom or after-hours.
234 //cout << "In Init(), initialState = " << initialState << endl;
235 operatingState = initialState;
237 switch(operatingState) {
239 tuned_station = ground;
240 ourGate = ground->GetGateNode();
241 if(ourGate == NULL) {
242 // Implies no available gates - what shall we do?
243 // For now just vanish the plane - possibly we can make this more elegant in the future
244 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
252 _pos.setElevationM(aptElev);
253 _hdg = ourGate->heading;
256 // Now we've set the position we can do the ground elev
257 elevInitGood = false;
263 //tuned_station = ground;
264 // FIXME - implement this case properly
265 // For now we'll assume that the plane should start at the hold short in this case
266 // and that we're working without ground network elements. Ie. an airport with no facility file.
268 tuned_station = tower;
270 tuned_station = NULL;
273 // Set a position and orientation in an approximate place for hold short.
274 //cout << "rwy.width = " << rwy.width << '\n';
275 orthopos = SGVec3d((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
276 // TODO - set the x pos to be +ve if a RH parallel rwy.
277 _pos = ortho.ConvertFromLocal(orthopos);
278 _pos.setElevationM(aptElev);
279 _hdg = rwy.hdg + 90.0;
280 // TODO - reset the heading if RH rwy.
285 elevInitGood = false;
291 responseCounter = 0.0;
292 contactTower = false;
295 clearedToLineUp = false;
296 changeFreqType = TOWER;
300 // For now we'll always start the in_pattern case on the threshold ready to take-off
301 // since we've got the implementation for this case already.
302 // TODO - implement proper generic in_pattern startup.
304 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
306 //cout << "Starting in pattern...\n";
309 tuned_station = tower;
311 tuned_station = NULL;
314 circuitsToFly = 0; // ie just fly this circuit and then stop
317 if(initialLeg == DOWNWIND) {
318 _pos = ortho.ConvertFromLocal(SGVec3d(1000*patternDirection, 800, 0.0));
319 _pos.setElevationM(rwy.threshold_pos.getElevationM() + 1000 * SG_FEET_TO_METER);
320 _hdg = rwy.hdg + 180.0;
322 elevInitGood = false;
324 SetTrack(rwy.hdg - (180 * patternDirection));
330 _aip.setVisible(true);
332 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
336 // Default to initial position on threshold for now
337 _pos = rwy.threshold_pos;
340 // Now we've set the position we can do the ground elev
341 // This might not always be necessary if we implement in-air start
342 elevInitGood = false;
355 operatingState = IN_PATTERN;
359 // This implies we're being init'd by AIGAVFRTraffic - simple return now
362 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
371 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
372 void FGAILocalTraffic::DownwindEntry() {
373 circuitsToFly = 0; // ie just fly this circuit and then stop
375 operatingState = IN_PATTERN;
377 elevInitGood = false;
379 SetTrack(rwy.hdg - (180 * patternDirection));
387 void FGAILocalTraffic::StraightInEntry(bool des) {
388 //cout << "************ STRAIGHT-IN ********************\n";
389 circuitsToFly = 0; // ie just fly this circuit and then stop
391 operatingState = IN_PATTERN;
393 elevInitGood = false;
396 transmitted = true; // TODO - fix this hack.
397 // TODO - set up the next 5 properly for a descent!
406 // Return what type of landing we're doing on this circuit
407 LandingType FGAILocalTraffic::GetLandingOption() {
408 //cout << "circuitsToFly = " << circuitsToFly << '\n';
410 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
417 // Commands to do something from higher level logic
418 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
419 //cout << "FlyCircuits called" << endl;
421 switch(operatingState) {
423 circuitsToFly += numCircuits;
427 // HACK - assume that we're taxiing out for now
428 circuitsToFly += numCircuits;
432 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
433 // thus flying one too many circuits. TODO - Need to sort this out better!
441 // Run the internal calculations
442 void FGAILocalTraffic::Update(double dt) {
443 //cout << "U" << flush;
445 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
446 // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
448 if(dclGetHorizontalSeparation(_pos, SGGeod::fromDegM(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
449 else _aip.setVisible(true);
451 _aip.setVisible(false);
454 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
455 responseCounter += dt;
456 if((contactTower) && (responseCounter >= 8.0)) {
457 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
458 string trns = "Tower ";
459 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
461 sprintf(buf, "%.2f", f);
464 trns += plane.callsign;
465 pending_transmission = trns;
466 ConditionalTransmit(30.0);
467 responseCounter = 0.0;
468 contactTower = false;
470 changeFreqType = TOWER;
473 if((contactGround) && (responseCounter >= 8.0)) {
474 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
475 string trns = "Ground ";
476 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
478 sprintf(buf, "%.2f", f);
482 pending_transmission = trns;
483 ConditionalTransmit(5.0);
484 responseCounter = 0.0;
485 contactGround = false;
487 changeFreqType = GROUND;
490 if((_taxiToGA) && (responseCounter >= 8.0)) {
491 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
492 string trns = "GA Parking, Thank you and Good Day";
493 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
494 pending_transmission = trns;
495 ConditionalTransmit(5.0, 99);
498 tower->DeregisterAIPlane(plane.callsign);
500 // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
503 if((_removeSelf) && (responseCounter >= 8.0)) {
505 // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
506 // TODO FIXME TODO FIXME !!!!!!!
507 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
510 if((changeFreq) && (responseCounter > 8.0)) {
511 switch(changeFreqType) {
514 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
517 tuned_station = tower;
518 freq = (double)tower->get_freq() / 100.0;
520 // Contact the tower, even if only virtually
521 pending_transmission = plane.callsign;
522 pending_transmission += " at hold short for runway ";
523 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
524 pending_transmission += " traffic pattern ";
526 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
527 pending_transmission += " circuits touch and go";
529 pending_transmission += " one circuit to full stop";
535 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
539 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
542 tower->DeregisterAIPlane(plane.callsign);
543 tuned_station = ground;
544 freq = (double)ground->get_freq() / 100.0;
546 // And to avoid compiler warnings...
547 case APPROACH: break;
551 case DEPARTURE: break;
557 //cout << "," << flush;
559 switch(operatingState) {
561 //cout << "In IN_PATTERN\n";
565 if(_ground_elevation_m > -9990.0) {
566 _pos.setElevationM(_ground_elevation_m + wheelOffset);
567 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
569 _aip.setVisible(true);
570 //cout << "Making plane visible!\n";
575 FlyTrafficPattern(dt);
579 //cout << "In TAXIING\n";
580 //cout << "*" << flush;
583 if(_ground_elevation_m > -9990.0) {
584 _pos.setElevationM(_ground_elevation_m + wheelOffset);
586 _aip.setVisible(true);
588 //cout << "Making plane visible!\n";
593 //cout << "~" << flush;
594 if(!((holdingShort) && (!clearedToLineUp))) {
595 //cout << "|" << flush;
598 //cout << ";" << flush;
599 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
600 // possible assumption that we're at the hold short here - may not always hold
601 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
602 taxiState = TD_LINING_UP;
603 //cout << "A" << endl;
604 path = ground->GetPath(holdShortNode, rwy.rwyID);
605 //cout << "B" << endl;
606 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
607 //cout << "C" << endl;
609 np->struct_type = NODE;
610 np->pos = ortho.ConvertFromLocal(SGVec3d(0.0, 10.0, 0.0));
613 //cout << "D" << endl;
616 cout << "path returned was:" << endl;
617 for(unsigned int i=0; i<path.size(); ++i) {
618 switch(path[i]->struct_type) {
620 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
628 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
629 holdingShort = false;
630 string trns = "Cleared for take-off ";
631 trns += plane.callsign;
632 pending_transmission = trns;
636 //cout << "^" << flush;
640 //cout << "In PARKED\n";
643 if(_ground_elevation_m > -9990.0) {
644 _pos.setElevationM(_ground_elevation_m + wheelOffset);
646 _aip.setVisible(true);
648 //cout << "Making plane visible!\n";
654 if((taxiRequestPending) && (taxiRequestCleared)) {
655 //cout << "&" << flush;
656 // Get the active runway details (in case they've changed since init)
657 GetRwyDetails(airportID);
659 // Get the takeoff node for the active runway, get a path to it and start taxiing
660 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
661 if(path.size() < 2) {
662 // something has gone wrong
663 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
667 cout << "path returned was:\n";
668 for(unsigned int i=0; i<path.size(); ++i) {
669 switch(path[i]->struct_type) {
671 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
679 path.erase(path.begin()); // pop the gate - we're here already!
680 taxiState = TD_OUTBOUND;
681 taxiRequestPending = false;
682 holdShortNode = (node*)(*(path.begin() + path.size()));
684 } else if(!taxiRequestPending) {
685 //cout << "(" << flush;
686 // Do some communication
687 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
690 trns += tower->get_name();
694 // TODO - get the airport name somehow if uncontrolled
696 trns += plane.callsign;
697 trns += " on apron parking request taxi for traffic pattern";
698 //cout << "trns = " << trns << endl;
699 pending_transmission = trns;
701 taxiRequestCleared = false;
702 taxiRequestPending = true;
706 //cout << "!" << flush;
708 // Maybe the below should be set when we get to the threshold and prepare for TO?
709 // FIXME TODO - pattern direction is still hardwired
710 patternDirection = -1; // Left
711 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
712 if(rwy.rwyID.size() == 3) {
713 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
717 //cout << ")" << flush;
722 //cout << "I " << flush;
724 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
726 // Convienience output for AI debugging using the property logger
727 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
728 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
729 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
731 // And finally, call parent.
732 FGAIPlane::Update(dt);
735 void FGAILocalTraffic::RegisterTransmission(int code) {
737 case 1: // taxi request cleared
738 taxiRequestCleared = true;
739 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
741 case 2: // contact tower
744 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
746 case 3: // Cleared to line up
748 clearedToLineUp = true;
749 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
751 case 4: // cleared to take-off
753 clearedToTakeOff = true;
754 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
756 case 5: // contact ground
758 contactGround = true;
759 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
761 // case 6 is a temporary mega-hack for controlled airports without separate ground control
762 case 6: // taxi to the GA parking
765 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
767 case 7: // Cleared to land (also implies cleared for the option
768 _clearedToLand = true;
769 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
771 case 13: // Go around!
774 _clearedToLand = false;
775 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
782 // Fly a traffic pattern
783 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
784 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
785 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
786 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
787 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
790 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
791 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
793 //cout << "dt = " << dt << '\n';
795 // ack - I can't remember how long a rate 1 turn is meant to take.
796 double turn_time = 60.0; // seconds - TODO - check this guess
797 double turn_circumference;
799 SGVec3d orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
800 //cout << "runway elev = " << rwy.threshold_pos.getElevationM() << ' ' << rwy.threshold_pos.getElevationM() * SG_METER_TO_FEET << '\n';
801 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
803 // HACK FOR TESTING - REMOVE
804 //cout << "Calling ExitRunway..." << endl;
805 //ExitRunway(orthopos);
810 double wind_from = wind_from_hdg->getDoubleValue();
811 double wind_speed = wind_speed_knots->getDoubleValue();
823 if(_ground_elevation_m > -9990.0) {
824 _pos.setElevationM(_ground_elevation_m + wheelOffset);
826 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
829 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
831 IAS = best_rate_of_climb_speed;
833 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
838 // Turn to crosswind if above 700ft AND if other traffic allows
839 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
840 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
841 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
842 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 700) {
844 if(tower->GetCrosswindConstraint(cc)) {
845 if(orthopos.y() > cc) {
846 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
849 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
850 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
851 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
855 // Need to check for levelling off in case we can't turn crosswind as soon
856 // as we would like due to other traffic.
857 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
860 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
862 if(goAround && !goAroundCalled) {
863 if(responseCounter > 5.5) {
864 pending_transmission = plane.callsign;
865 pending_transmission += " going around";
867 goAroundCalled = true;
872 SetTrack(rwy.hdg + (90.0 * patternDirection));
873 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
879 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
882 IAS = 80.0; // FIXME - use smooth transistion to new speed
884 // turn 1000m out for now, taking other traffic into accout
885 if(fabs(orthopos.x()) > 900) {
887 if(tower->GetDownwindConstraint(dd)) {
888 if(fabs(orthopos.x()) > fabs(dd)) {
889 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
893 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
899 SetTrack(rwy.hdg - (180 * patternDirection));
900 // just in case we didn't make height on crosswind
901 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
904 IAS = 80.0; // FIXME - use smooth transistion to new speed
906 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
912 // just in case we didn't make height on crosswind
913 if(((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 995) && ((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET < 1015)) {
916 IAS = 90.0; // FIXME - use smooth transistion to new speed
918 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET >= 1015) {
921 IAS = 90.0; // FIXME - use smooth transistion to new speed
923 if((orthopos.y() < 0) && (!transmitted)) {
924 TransmitPatternPositionReport();
927 if((orthopos.y() < -100) && (!descending)) {
928 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
929 // Maybe we should think about when to start descending.
930 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
933 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (tower->GetDownwindConstraint(d2) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
934 if(SoD.leg == DOWNWIND) {
935 descending = (orthopos.y() < SoD.y ? true : false);
940 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
945 // Try and arrange to turn nicely onto base
946 turn_circumference = IAS * 0.514444 * turn_time;
947 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
948 //We'll leave it as a hack with IAS for now but it needs revisiting.
949 turn_radius = turn_circumference / (2.0 * DCL_PI);
950 if(orthopos.y() < -1000.0 + turn_radius) {
951 //if(orthopos.y() < -980) {
953 if(tower->GetBaseConstraint(bb)) {
954 if(fabs(orthopos.y()) > fabs(bb)) {
955 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
961 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
969 SetTrack(rwy.hdg - (90 * patternDirection));
970 if(fabs(rwy.hdg - track) < 91.0) {
976 // Base report should only be transmitted at uncontrolled airport - not towered.
977 if(!_controlled) TransmitPatternPositionReport();
983 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
984 // on downwind when we are already on base.
985 CalculateSoD((tower->GetBaseConstraint(d1) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
986 if(SoD.leg == BASE) {
987 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
992 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
997 // Try and arrange to turn nicely onto final
998 turn_circumference = IAS * 0.514444 * turn_time;
999 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1000 //We'll leave it as a hack with IAS for now but it needs revisiting.
1001 turn_radius = turn_circumference / (2.0 * DCL_PI);
1002 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1004 transmitted = false;
1010 if(fabs(track - rwy.hdg) < 0.6) {
1012 vel = nominal_final_speed;
1016 if(goAround && responseCounter > 2.0) {
1019 IAS = best_rate_of_climb_speed;
1020 slope = 5.0; // A bit less steep than the initial climbout.
1022 goAroundCalled = false;
1028 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1032 // Make base leg position artifically large to avoid any chance of SoD being returned as
1033 // on base or downwind when we are already on final.
1034 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1035 if(SoD.leg == FINAL) {
1036 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1041 if(orthopos.y() < -50.0) {
1042 double thesh_offset = 30.0;
1043 slope = atan((_pos.getElevationM() - fgGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1044 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << fgGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1045 if(slope < -10.0) slope = -10.0;
1046 _savedSlope = slope;
1050 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1051 if(_ground_elevation_m > -9990.0) {
1052 if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 1.0)) {
1056 } else if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 5.0)) {
1061 slope = _savedSlope;
1066 // Elev not determined
1067 slope = _savedSlope;
1072 slope = _savedSlope;
1078 // Try and track the extended centreline
1079 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1080 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1081 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+20.0+wheelOffset)) {
1082 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1083 // for us in update(...) when the inAir flag is false.
1085 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1088 if(_ground_elevation_m > -9990.0) {
1089 if((_ground_elevation_m + wheelOffset) > _pos.getElevationM()) {
1095 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1097 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1105 if(_ground_elevation_m > -9990.0) {
1106 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1111 // FIXME - differentiate between touch and go and full stops
1113 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1114 if(circuitsToFly <= 0) {
1115 //cout << "Calling ExitRunway..." << endl;
1116 ExitRunway(orthopos);
1119 //cout << "Taking off again..." << endl;
1130 // FIXME - at the moment this is a bit screwy
1131 // The velocity correction is applied based on the relative headings.
1132 // Then the heading is changed based on the velocity.
1133 // Which comes first, the chicken or the egg?
1134 // Does it really matter?
1136 // Apply wind to ground-relative velocity if in the air
1137 vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
1138 //crab = f(track, wind, vel);
1139 // The vector we need to fly is our desired vector minus the wind vector
1140 // TODO - we probably ought to use plib's built in vector types and operations for this
1141 // ie. There's almost *certainly* a better way to do this!
1142 double gxx = vel * sin(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity x component wrt ground
1143 double gyy = vel * cos(track * DCL_DEGREES_TO_RADIANS); // Plane desired velocity y component wrt ground
1144 double wxx = wind_speed * sin((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity x component
1145 double wyy = wind_speed * cos((wind_from + 180.0) * DCL_DEGREES_TO_RADIANS); // Wind velocity y component
1146 double axx = gxx - wxx; // Plane in-air velocity x component
1147 double ayy = gyy - wyy; // Plane in-air velocity y component
1148 // Now we want the angle between gxx and axx (which is the crab)
1149 double maga = sqrt(axx*axx + ayy*ayy);
1150 double magg = sqrt(gxx*gxx + gyy*gyy);
1151 crab = acos((axx*gxx + ayy*gyy) / (maga * magg));
1152 // At this point this works except we're getting the modulus of the angle
1153 //cout << "crab = " << crab << '\n';
1155 // Make sure both headings are in the 0->360 circle in order to get sane differences
1156 dclBoundHeading(wind_from);
1157 dclBoundHeading(track);
1158 if(track > wind_from) {
1159 if((track - wind_from) <= 180) {
1163 if((wind_from - track) >= 180) {
1167 } else { // on the ground - crab dosen't apply
1171 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1173 _hdg = track + crab;
1174 dist = vel * 0.514444 * dt;
1175 _pos = dclUpdatePosition(_pos, track, slope, dist);
1178 // Pattern direction is true for right, false for left
1179 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1180 // For now we'll ignore wind and hardwire the glide angle.
1181 double ga = 5.5; //degrees
1182 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1183 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1185 // For convienience, we'll have +ve versions of the input distances
1186 double blp = fabs(base_leg_pos);
1187 double dlp = fabs(downwind_leg_pos);
1189 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1191 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1192 //cout << "Descent to start = " << stod << " meters out\n";
1193 if(stod < blp) { // Start descending on final
1195 SoD.y = stod * -1.0;
1197 } else if(stod < (blp + dlp)) { // Start descending on base leg
1200 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1201 } else { // Start descending on downwind leg
1203 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1204 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1208 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1209 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1213 trns += tower->get_name();
1214 trns += " Traffic ";
1215 trns += plane.callsign;
1216 if(patternDirection == 1) {
1222 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1223 switch(leg) { // We'll assume that transmissions in turns are intended for next leg - do pilots ever call out that they are in the turn?
1225 // Fall through to CROSSWIND
1226 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1227 trns += "crosswind ";
1230 // Fall through to DOWNWIND
1232 trns += "downwind ";
1236 // Fall through to BASE
1241 // Fall through to FINAL
1242 case FINAL: // maybe this should include long/short final if appropriate?
1246 default: // Hopefully this won't be used
1250 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1254 // And add the airport name again
1255 trns += tower->get_name();
1257 pending_transmission = trns;
1258 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1262 // TODO - Really should enumerate these coded values.
1263 void FGAILocalTraffic::ProcessCallback(int code) {
1264 // 1 - Request Departure from ground
1265 // 2 - Report at hold short
1266 // 3 - Report runway vacated
1267 // 10 - report crosswind
1268 // 11 - report downwind
1270 // 13 - report final
1272 ground->RequestDeparture(plane, this);
1273 } else if(code == 2) {
1274 tower->ContactAtHoldShort(plane, this, CIRCUIT);
1275 } else if(code == 3) {
1276 tower->ReportRunwayVacated(plane.callsign);
1277 } else if(code == 11) {
1278 tower->ReportDownwind(plane.callsign);
1279 } else if(code == 13) {
1280 tower->ReportFinal(plane.callsign);
1281 } else if(code == 99) { // Flag this instance for deletion
1282 responseCounter = 0;
1284 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
1288 void FGAILocalTraffic::ExitRunway(const SGVec3d& orthopos) {
1289 //cout << "In ExitRunway" << endl;
1290 //cout << "Runway ID is " << rwy.ID << endl;
1292 _clearedToLand = false;
1294 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1296 cout << "Node ID's of exits are ";
1297 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1298 cout << exitNodes[i]->nodeID << ' ';
1302 if(exitNodes.size()) {
1303 //Find the next exit from orthopos.y
1305 double dist = 100000; //ie. longer than any runway in existance
1306 double backdist = 100000;
1307 node_array_iterator nItr = exitNodes.begin();
1308 node* rwyExit = *(exitNodes.begin());
1309 //int gateID; //This might want to be more persistant at some point
1310 while(nItr != exitNodes.end()) {
1311 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1318 if(fabs(d) < backdist) {
1320 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1325 ourGate = ground->GetGateNode();
1326 if(ourGate == NULL) {
1327 // Implies no available gates - what shall we do?
1328 // For now just vanish the plane - possibly we can make this more elegant in the future
1329 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1330 //_aip.setVisible(false);
1331 //cout << "Setting visible false\n";
1332 operatingState = PARKED;
1335 path = ground->GetPath(rwyExit, ourGate);
1337 cout << "path returned was:" << endl;
1338 for(unsigned int i=0; i<path.size(); ++i) {
1339 switch(path[i]->struct_type) {
1341 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1349 taxiState = TD_INBOUND;
1352 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1353 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1354 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1355 // What shall we do - just remove the plane from sight?
1356 _aip.setVisible(false);
1358 //cout << "Setting visible false\n";
1359 //tower->ReportRunwayVacated(plane.callsign);
1360 string trns = "Clear of the runway ";
1361 trns += plane.callsign;
1362 pending_transmission = trns;
1364 operatingState = PARKED;
1368 // Set the class variable nextTaxiNode to the next node in the path
1369 // and update taxiPathPos, the class variable path iterator position
1370 // TODO - maybe should return error codes to the calling function if we fail here
1371 void FGAILocalTraffic::GetNextTaxiNode() {
1372 //cout << "GetNextTaxiNode called " << endl;
1373 //cout << "taxiPathPos = " << taxiPathPos << endl;
1374 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1375 if(pathItr == path.end()) {
1376 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1378 if((*pathItr)->struct_type == NODE) {
1379 //cout << "ITS A NODE" << endl;
1380 //*pathItr = new node;
1381 nextTaxiNode = (node*)*pathItr;
1385 //cout << "ITS NOT A NODE" << endl;
1386 //The first item in found must have been an arc
1387 //Assume for now that it was straight
1390 if(pathItr == path.end()) {
1391 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1392 } else if((*pathItr)->struct_type == NODE) {
1393 nextTaxiNode = (node*)*pathItr;
1396 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1397 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1403 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1404 void FGAILocalTraffic::StartTaxi() {
1405 //cout << "StartTaxi called" << endl;
1406 operatingState = TAXIING;
1410 //Set the desired heading
1411 //Assume we are aiming for first node on path
1412 //Eventually we may need to consider the fact that we might start on a curved arc and
1413 //not be able to head directly for the first node.
1414 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1415 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1416 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1419 // speed in knots, headings in degrees, radius in meters.
1420 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1421 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1422 while(current_hdg < 0.0) {
1423 current_hdg += 360.0;
1425 while(current_hdg > 360.0) {
1426 current_hdg -= 360.0;
1428 if(fabs(current_hdg - desired_hdg) > 0.1) {
1429 // Which is the quickest direction to turn onto heading?
1430 if(desired_hdg > current_hdg) {
1431 if((desired_hdg - current_hdg) <= 180) {
1433 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1434 // TODO - check that increments are less than the delta that we check for the right direction
1435 // Probably need to reduce convergence speed as convergence is reached
1437 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1440 if((current_hdg - desired_hdg) <= 180) {
1442 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1443 // TODO - check that increments are less than the delta that we check for the right direction
1444 // Probably need to reduce convergence speed as convergence is reached
1446 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1450 return(current_hdg);
1453 void FGAILocalTraffic::Taxi(double dt) {
1454 //cout << "Taxi called" << endl;
1455 // Logic - if we are further away from next point than turn radius then head for it
1456 // If we have reached turning point then get next point and turn onto that heading
1457 // Look out for the finish!!
1459 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1461 bool lastNode = (taxiPathPos == path.size() ? true : false);
1463 //cout << "LAST NODE\n";
1466 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1468 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1469 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1470 //cout << "dist_to_go = " << dist_to_go << endl;
1471 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1472 // This might be more robust to outward paths starting with a gate if we check for either
1473 // last node or TD_INBOUND ?
1475 operatingState = PARKED;
1476 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1477 // if the turn radius is r, and speed is s, then in a time dt we turn through
1478 // ((s.dt)/(PI.r)) x 180 degrees
1479 // or alternatively (s.dt)/r radians
1480 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1481 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1482 double vel = nominalTaxiSpeed;
1483 //cout << "vel = " << vel << endl;
1484 double dist = vel * 0.514444 * dt;
1485 //cout << "dist = " << dist << endl;
1486 double track = _hdg;
1487 //cout << "track = " << track << endl;
1489 _pos = dclUpdatePosition(_pos, track, slope, dist);
1490 //cout << "Updated position...\n";
1491 if(_ground_elevation_m > -9990) {
1492 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1493 } // else don't change the elev until we get a valid ground elev again!
1494 } else if(lastNode) {
1495 if(taxiState == TD_LINING_UP) {
1496 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1500 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1501 double vel = nominalTaxiSpeed;
1502 //cout << "vel = " << vel << endl;
1503 double dist = vel * 0.514444 * dt;
1504 //cout << "dist = " << dist << endl;
1505 double track = _hdg;
1506 //cout << "track = " << track << endl;
1508 _pos = dclUpdatePosition(_pos, track, slope, dist);
1509 //cout << "Updated position...\n";
1510 if(_ground_elevation_m > -9990) {
1511 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1512 } // else don't change the elev until we get a valid ground elev again!
1513 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1514 operatingState = IN_PATTERN;
1520 } else if(taxiState == TD_OUTBOUND) {
1521 // Pause awaiting further instructions
1522 // and for now assume we've reached the hold-short node
1523 holdingShort = true;
1524 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1526 // Time to turn (we've already checked it's not the end we're heading for).
1527 // set the target node to be the next node which will prompt automatically turning onto
1528 // the right heading in the stuff above, with the usual provisos applied.
1530 // For now why not just recursively call this function?
1536 // Warning - ground elev determination is CPU intensive
1537 // Either this function or the logic of how often it is called
1538 // will almost certainly change.
1539 void FGAILocalTraffic::DoGroundElev() {
1540 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1541 double visibility_meters = fgGetDouble("/environment/visibility-m");
1542 FGViewer* vw = globals->get_current_view();
1543 if(dclGetHorizontalSeparation(_pos, SGGeod::fromGeodM(vw->getPosition(), 0.0)) > visibility_meters) {
1544 _ground_elevation_m = aptElev;
1548 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1549 double range = 500.0;
1550 if (!globals->get_tile_mgr()->scenery_available(_aip.getPosition(), range)) {
1551 // Try to shedule tiles for that position.
1552 globals->get_tile_mgr()->update( _aip.getPosition(), range );
1555 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1557 if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(_aip.getPosition(), 20000), alt, 0, _aip.getSceneGraph()))
1558 _ground_elevation_m = alt;