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;
116 holdShortNode = NULL;
119 FGAILocalTraffic::~FGAILocalTraffic() {
122 void FGAILocalTraffic::GetAirportDetails(const string& id) {
124 if(ATC->GetAirportATCDetails(airportID, &a)) {
125 if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!!
126 tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER);
128 // Something has gone wrong - abort or carry on with un-towered operation?
129 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
135 ground = tower->GetGroundPtr();
137 // Something has gone wrong :-(
138 SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
143 // TODO - Check CTAF, unicom etc
146 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
149 // Get the airport elevation
150 aptElev = fgGetAirportElev(airportID.c_str());
151 //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
152 // WARNING - we use this elev for the whole airport - some assumptions in the code
153 // might fall down with very slopey airports.
156 // Get details of the active runway
157 // It is assumed that by the time this is called the tower control and airport code will have been set up.
158 void FGAILocalTraffic::GetRwyDetails(const string& id) {
159 //cout << "GetRwyDetails called" << endl;
161 const FGAirport* apt = fgFindAirportID(id);
163 FGRunway* runway(apt->getActiveRunwayForUsage());
165 double hdg = runway->headingDeg();
166 double other_way = hdg - 180.0;
167 while(other_way <= 0.0) {
171 // move to the +l end/center of the runway
172 //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
173 double tshlon = 0.0, tshlat = 0.0, tshr;
174 double tolon = 0.0, tolat = 0.0, tor;
175 rwy.length = runway->lengthM();
176 rwy.width = runway->widthM();
177 geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way,
178 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
179 geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), hdg,
180 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
181 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
182 // now copy what we need out of runway into rwy
183 rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev);
184 SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev);
185 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
186 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
188 // Set the projection for the local area
189 //cout << "Initing ortho for airport " << id << '\n';
190 ortho.Init(rwy.threshold_pos, rwy.hdg);
191 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
192 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
197 There are two possible scenarios during initialisation:
198 The first is that the user is flying towards the airport, and hence the traffic
199 could be initialised anywhere, as long as the AI planes are consistent with
201 The second is that the user has started the sim at or close to the airport, and
202 hence the traffic must be initialised with respect to the user as well as each other.
203 To a certain extent it's FGAIMgr that has to worry about this, but we need to provide
204 sufficient initialisation functionality within the plane classes to allow the manager
205 to initially position them where and how required.
207 bool FGAILocalTraffic::Init(const string& callsign, const string& ICAO, OperatingState initialState, PatternLeg initialLeg) {
208 //cout << "FGAILocalTraffic.Init(...) called" << endl;
211 plane.callsign = callsign;
213 if(initialState == EN_ROUTE) return(true);
215 // Get the ATC pointers and airport elev
216 GetAirportDetails(airportID);
218 // Get the active runway details (and copy them into rwy)
219 GetRwyDetails(airportID);
220 //cout << "Runway is " << rwy.rwyID << '\n';
222 // FIXME TODO - pattern direction is still hardwired
223 patternDirection = -1; // Left
224 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
225 if(rwy.rwyID.size() == 3) {
226 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
230 if((initialState == PARKED) || (initialState == TAXIING)) {
231 freq = (double)ground->get_freq() / 100.0;
233 freq = (double)tower->get_freq() / 100.0;
237 // TODO - find the proper freq if CTAF or unicom or after-hours.
240 //cout << "In Init(), initialState = " << initialState << endl;
241 operatingState = initialState;
243 switch(operatingState) {
245 tuned_station = ground;
246 ourGate = ground->GetGateNode();
247 if(ourGate == NULL) {
248 // Implies no available gates - what shall we do?
249 // For now just vanish the plane - possibly we can make this more elegant in the future
250 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
258 _pos.setElevationM(aptElev);
259 _hdg = ourGate->heading;
262 // Now we've set the position we can do the ground elev
263 elevInitGood = false;
269 //tuned_station = ground;
270 // FIXME - implement this case properly
271 // For now we'll assume that the plane should start at the hold short in this case
272 // and that we're working without ground network elements. Ie. an airport with no facility file.
274 tuned_station = tower;
276 tuned_station = NULL;
279 // Set a position and orientation in an approximate place for hold short.
280 //cout << "rwy.width = " << rwy.width << '\n';
281 orthopos = SGVec3d((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
282 // TODO - set the x pos to be +ve if a RH parallel rwy.
283 _pos = ortho.ConvertFromLocal(orthopos);
284 _pos.setElevationM(aptElev);
285 _hdg = rwy.hdg + 90.0;
286 // TODO - reset the heading if RH rwy.
291 elevInitGood = false;
297 responseCounter = 0.0;
298 contactTower = false;
301 clearedToLineUp = false;
302 changeFreqType = TOWER;
306 // For now we'll always start the in_pattern case on the threshold ready to take-off
307 // since we've got the implementation for this case already.
308 // TODO - implement proper generic in_pattern startup.
310 // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
312 //cout << "Starting in pattern...\n";
315 tuned_station = tower;
317 tuned_station = NULL;
320 circuitsToFly = 0; // ie just fly this circuit and then stop
323 if(initialLeg == DOWNWIND) {
324 _pos = ortho.ConvertFromLocal(SGVec3d(1000*patternDirection, 800, 0.0));
325 _pos.setElevationM(rwy.threshold_pos.getElevationM() + 1000 * SG_FEET_TO_METER);
326 _hdg = rwy.hdg + 180.0;
328 elevInitGood = false;
330 SetTrack(rwy.hdg - (180 * patternDirection));
336 _aip.setVisible(true);
338 tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
342 // Default to initial position on threshold for now
343 _pos = rwy.threshold_pos;
346 // Now we've set the position we can do the ground elev
347 // This might not always be necessary if we implement in-air start
348 elevInitGood = false;
361 operatingState = IN_PATTERN;
365 // This implies we're being init'd by AIGAVFRTraffic - simple return now
368 SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
377 // Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
378 void FGAILocalTraffic::DownwindEntry() {
379 circuitsToFly = 0; // ie just fly this circuit and then stop
381 operatingState = IN_PATTERN;
383 elevInitGood = false;
385 SetTrack(rwy.hdg - (180 * patternDirection));
393 void FGAILocalTraffic::StraightInEntry(bool des) {
394 //cout << "************ STRAIGHT-IN ********************\n";
395 circuitsToFly = 0; // ie just fly this circuit and then stop
397 operatingState = IN_PATTERN;
399 elevInitGood = false;
402 transmitted = true; // TODO - fix this hack.
403 // TODO - set up the next 5 properly for a descent!
412 // Return what type of landing we're doing on this circuit
413 LandingType FGAILocalTraffic::GetLandingOption() {
414 //cout << "circuitsToFly = " << circuitsToFly << '\n';
416 return(touchAndGo ? TOUCH_AND_GO : STOP_AND_GO);
423 // Commands to do something from higher level logic
424 void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
425 //cout << "FlyCircuits called" << endl;
427 switch(operatingState) {
429 circuitsToFly += numCircuits;
433 // HACK - assume that we're taxiing out for now
434 circuitsToFly += numCircuits;
438 circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
439 // thus flying one too many circuits. TODO - Need to sort this out better!
447 // Run the internal calculations
448 void FGAILocalTraffic::Update(double dt) {
449 //cout << "U" << flush;
451 // we shouldn't really need this since there's a LOD of 10K on the whole plane anyway I think.
452 // At the moment though I need to to avoid DList overflows - the whole plane LOD obviously isn't getting picked up.
454 if(dclGetHorizontalSeparation(_pos, SGGeod::fromDegM(fgGetDouble("/position/longitude-deg"), fgGetDouble("/position/latitude-deg"), 0.0)) > 8000) _aip.setVisible(false);
455 else _aip.setVisible(true);
457 _aip.setVisible(false);
460 //double responseTime = 10.0; // seconds - this should get more sophisticated at some point
461 responseCounter += dt;
462 if((contactTower) && (responseCounter >= 8.0)) {
463 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
464 string trns = "Tower ";
465 double f = globals->get_ATC_mgr()->GetFrequency(airportID, TOWER) / 100.0;
467 sprintf(buf, "%.2f", f);
470 trns += plane.callsign;
471 pending_transmission = trns;
472 ConditionalTransmit(30.0);
473 responseCounter = 0.0;
474 contactTower = false;
476 changeFreqType = TOWER;
479 if((contactGround) && (responseCounter >= 8.0)) {
480 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
481 string trns = "Ground ";
482 double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
484 sprintf(buf, "%.2f", f);
488 pending_transmission = trns;
489 ConditionalTransmit(5.0);
490 responseCounter = 0.0;
491 contactGround = false;
493 changeFreqType = GROUND;
496 if((_taxiToGA) && (responseCounter >= 8.0)) {
497 // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
498 string trns = "GA Parking, Thank you and Good Day";
499 //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;
500 pending_transmission = trns;
501 ConditionalTransmit(5.0, 99);
504 tower->DeregisterAIPlane(plane.callsign);
506 // NOTE - we can't delete this instance yet since then the frequency won't get release when the message display finishes.
509 if((_removeSelf) && (responseCounter >= 8.0)) {
511 // MEGA HACK - check if we are at a simple airport or not first instead of simply hardwiring KEMT as the only non-simple airport.
512 // TODO FIXME TODO FIXME !!!!!!!
513 if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
516 if((changeFreq) && (responseCounter > 8.0)) {
517 switch(changeFreqType) {
520 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to tower in FGAILocalTraffic, but tower is NULL!!!");
523 tuned_station = tower;
524 freq = (double)tower->get_freq() / 100.0;
526 // Contact the tower, even if only virtually
527 pending_transmission = plane.callsign;
528 pending_transmission += " at hold short for runway ";
529 pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
530 pending_transmission += " traffic pattern ";
532 pending_transmission += ConvertNumToSpokenDigits(circuitsToFly + 1);
533 pending_transmission += " circuits touch and go";
535 pending_transmission += " one circuit to full stop";
541 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but tower is NULL!!!");
545 SG_LOG(SG_ATC, SG_ALERT, "ERROR: Trying to change frequency to ground in FGAILocalTraffic, but ground is NULL!!!");
548 tower->DeregisterAIPlane(plane.callsign);
549 tuned_station = ground;
550 freq = (double)ground->get_freq() / 100.0;
552 // And to avoid compiler warnings...
553 case APPROACH: break;
557 case DEPARTURE: break;
563 //cout << "," << flush;
565 switch(operatingState) {
567 //cout << "In IN_PATTERN\n";
571 if(_ground_elevation_m > -9990.0) {
572 _pos.setElevationM(_ground_elevation_m + wheelOffset);
573 //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
575 _aip.setVisible(true);
576 //cout << "Making plane visible!\n";
581 FlyTrafficPattern(dt);
585 //cout << "In TAXIING\n";
586 //cout << "*" << flush;
589 if(_ground_elevation_m > -9990.0) {
590 _pos.setElevationM(_ground_elevation_m + wheelOffset);
592 _aip.setVisible(true);
594 //cout << "Making plane visible!\n";
599 //cout << "~" << flush;
600 if(!((holdingShort) && (!clearedToLineUp))) {
601 //cout << "|" << flush;
604 //cout << ";" << flush;
605 if((clearedToTakeOff) && (responseCounter >= 8.0)) {
606 // possible assumption that we're at the hold short here - may not always hold
607 // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
608 taxiState = TD_LINING_UP;
609 //cout << "A" << endl;
610 path = ground->GetPath(holdShortNode, rwy.rwyID);
611 //cout << "B" << endl;
612 if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold
613 //cout << "C" << endl;
615 np->struct_type = NODE;
616 np->pos = ortho.ConvertFromLocal(SGVec3d(0.0, 10.0, 0.0));
619 //cout << "D" << endl;
622 cout << "path returned was:" << endl;
623 for(unsigned int i=0; i<path.size(); ++i) {
624 switch(path[i]->struct_type) {
626 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
634 clearedToTakeOff = false; // We *are* still cleared - this simply stops the response recurring!!
635 holdingShort = false;
636 string trns = "Cleared for take-off ";
637 trns += plane.callsign;
638 pending_transmission = trns;
642 //cout << "^" << flush;
646 //cout << "In PARKED\n";
649 if(_ground_elevation_m > -9990.0) {
650 _pos.setElevationM(_ground_elevation_m + wheelOffset);
652 _aip.setVisible(true);
654 //cout << "Making plane visible!\n";
660 if((taxiRequestPending) && (taxiRequestCleared)) {
661 //cout << "&" << flush;
662 // Get the active runway details (in case they've changed since init)
663 GetRwyDetails(airportID);
665 // Get the takeoff node for the active runway, get a path to it and start taxiing
666 path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
667 if(path.size() < 2) {
668 // something has gone wrong
669 SG_LOG(SG_ATC, SG_ALERT, "Invalid path from gate to theshold in FGAILocalTraffic::FlyCircuits\n");
673 cout << "path returned was:\n";
674 for(unsigned int i=0; i<path.size(); ++i) {
675 switch(path[i]->struct_type) {
677 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
685 path.erase(path.begin()); // pop the gate - we're here already!
686 taxiState = TD_OUTBOUND;
687 taxiRequestPending = false;
688 holdShortNode = (node*)(*(path.begin() + path.size()));
690 } else if(!taxiRequestPending) {
691 //cout << "(" << flush;
692 // Do some communication
693 // airport name + tower + airplane callsign + location + request taxi for + operation type + ?
696 trns += tower->get_name();
700 // TODO - get the airport name somehow if uncontrolled
702 trns += plane.callsign;
703 trns += " on apron parking request taxi for traffic pattern";
704 //cout << "trns = " << trns << endl;
705 pending_transmission = trns;
707 taxiRequestCleared = false;
708 taxiRequestPending = true;
712 //cout << "!" << flush;
714 // Maybe the below should be set when we get to the threshold and prepare for TO?
715 // FIXME TODO - pattern direction is still hardwired
716 patternDirection = -1; // Left
717 // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
718 if(rwy.rwyID.size() == 3) {
719 patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
723 //cout << ")" << flush;
728 //cout << "I " << flush;
730 //cout << "Update _pos = " << _pos << ", vis = " << _aip.getVisible() << '\n';
732 // Convienience output for AI debugging using the property logger
733 //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
734 //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
735 //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
737 // And finally, call parent.
738 FGAIPlane::Update(dt);
741 void FGAILocalTraffic::RegisterTransmission(int code) {
743 case 1: // taxi request cleared
744 taxiRequestCleared = true;
745 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to taxi...");
747 case 2: // contact tower
750 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact tower...");
752 case 3: // Cleared to line up
754 clearedToLineUp = true;
755 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to line-up...");
757 case 4: // cleared to take-off
759 clearedToTakeOff = true;
760 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
762 case 5: // contact ground
764 contactGround = true;
765 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
767 // case 6 is a temporary mega-hack for controlled airports without separate ground control
768 case 6: // taxi to the GA parking
771 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
773 case 7: // Cleared to land (also implies cleared for the option
774 _clearedToLand = true;
775 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
777 case 13: // Go around!
780 _clearedToLand = false;
781 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
788 // Fly a traffic pattern
789 // FIXME - far too much of the mechanics of turning, rolling, accellerating, descending etc is in here.
790 // Move it out to FGAIPlane and have FlyTrafficPattern just specify what to do, not the implementation.
791 void FGAILocalTraffic::FlyTrafficPattern(double dt) {
792 // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
793 // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
796 // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
797 // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
799 //cout << "dt = " << dt << '\n';
801 // ack - I can't remember how long a rate 1 turn is meant to take.
802 double turn_time = 60.0; // seconds - TODO - check this guess
803 double turn_circumference;
805 SGVec3d orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane
806 //cout << "runway elev = " << rwy.threshold_pos.getElevationM() << ' ' << rwy.threshold_pos.getElevationM() * SG_METER_TO_FEET << '\n';
807 //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
809 // HACK FOR TESTING - REMOVE
810 //cout << "Calling ExitRunway..." << endl;
811 //ExitRunway(orthopos);
816 double wind_from = wind_from_hdg->getDoubleValue();
817 double wind_speed = wind_speed_knots->getDoubleValue();
829 if(_ground_elevation_m > -9990.0) {
830 _pos.setElevationM(_ground_elevation_m + wheelOffset);
832 IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
835 SetTrack(rwy.hdg); // Hands over control of turning to AIPlane
837 IAS = best_rate_of_climb_speed;
839 slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
844 // Turn to crosswind if above 700ft AND if other traffic allows
845 // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
846 // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
847 // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
848 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 700) {
850 if(_controlled && tower->GetCrosswindConstraint(cc)) {
851 if(orthopos.y() > cc) {
852 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
855 } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
856 // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
857 //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
861 // Need to check for levelling off in case we can't turn crosswind as soon
862 // as we would like due to other traffic.
863 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
866 IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
868 if(goAround && !goAroundCalled) {
869 if(responseCounter > 5.5) {
870 pending_transmission = plane.callsign;
871 pending_transmission += " going around";
873 goAroundCalled = true;
878 SetTrack(rwy.hdg + (90.0 * patternDirection));
879 if((track < (rwy.hdg - 89.0)) || (track > (rwy.hdg + 89.0))) {
885 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
888 IAS = 80.0; // FIXME - use smooth transistion to new speed
890 // turn 1000m out for now, taking other traffic into accout
891 if(fabs(orthopos.x()) > 900) {
893 if(_controlled && tower->GetDownwindConstraint(dd)) {
894 if(fabs(orthopos.x()) > fabs(dd)) {
895 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
899 //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n';
905 SetTrack(rwy.hdg - (180 * patternDirection));
906 // just in case we didn't make height on crosswind
907 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 1000) {
910 IAS = 80.0; // FIXME - use smooth transistion to new speed
912 if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
918 // just in case we didn't make height on crosswind
919 if(((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET > 995) && ((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET < 1015)) {
922 IAS = 90.0; // FIXME - use smooth transistion to new speed
924 if((_pos.getElevationM() - rwy.threshold_pos.getElevationM()) * SG_METER_TO_FEET >= 1015) {
927 IAS = 90.0; // FIXME - use smooth transistion to new speed
929 if((orthopos.y() < 0) && (!transmitted)) {
930 TransmitPatternPositionReport();
933 if((orthopos.y() < -100) && (!descending)) {
934 //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
935 // Maybe we should think about when to start descending.
936 // For now we're assuming that we aim to follow the same glidepath regardless of wind.
939 CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), ((_controlled && tower->GetDownwindConstraint(d2)) ? d2 : 1000.0 * patternDirection), (patternDirection ? true : false));
940 if(SoD.leg == DOWNWIND) {
941 descending = (orthopos.y() < SoD.y ? true : false);
946 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
951 // Try and arrange to turn nicely onto base
952 turn_circumference = IAS * 0.514444 * turn_time;
953 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
954 //We'll leave it as a hack with IAS for now but it needs revisiting.
955 turn_radius = turn_circumference / (2.0 * DCL_PI);
956 if(orthopos.y() < -1000.0 + turn_radius) {
957 //if(orthopos.y() < -980) {
959 if(_controlled && tower->GetBaseConstraint(bb)) {
960 if(fabs(orthopos.y()) > fabs(bb)) {
961 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
967 //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n';
975 SetTrack(rwy.hdg - (90 * patternDirection));
976 if(fabs(rwy.hdg - track) < 91.0) {
982 // Base report should only be transmitted at uncontrolled airport - not towered.
983 if(!_controlled) TransmitPatternPositionReport();
989 // Make downwind leg position artifically large to avoid any chance of SoD being returned as
990 // on downwind when we are already on base.
991 CalculateSoD(((_controlled && tower->GetBaseConstraint(d1)) ? d1 : -1000.0), (10000.0 * patternDirection), (patternDirection ? true : false));
992 if(SoD.leg == BASE) {
993 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
998 slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
1003 // Try and arrange to turn nicely onto final
1004 turn_circumference = IAS * 0.514444 * turn_time;
1005 //Hmmm - this is an interesting one - ground vs airspeed in relation to turn radius
1006 //We'll leave it as a hack with IAS for now but it needs revisiting.
1007 turn_radius = turn_circumference / (2.0 * DCL_PI);
1008 if(fabs(orthopos.x()) < (turn_radius + 50)) {
1010 transmitted = false;
1016 if(fabs(track - rwy.hdg) < 0.6) {
1018 vel = nominal_final_speed;
1022 if(goAround && responseCounter > 2.0) {
1025 IAS = best_rate_of_climb_speed;
1026 slope = 5.0; // A bit less steep than the initial climbout.
1028 goAroundCalled = false;
1034 if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
1038 // Make base leg position artifically large to avoid any chance of SoD being returned as
1039 // on base or downwind when we are already on final.
1040 CalculateSoD(-10000.0, (1000.0 * patternDirection), (patternDirection ? true : false));
1041 if(SoD.leg == FINAL) {
1042 descending = (fabs(orthopos.y()) < fabs(SoD.y) ? true : false);
1047 if(orthopos.y() < -50.0) {
1048 double thesh_offset = 30.0;
1049 slope = atan((_pos.getElevationM() - fgGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
1050 //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << fgGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
1051 if(slope < -10.0) slope = -10.0;
1052 _savedSlope = slope;
1056 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1057 if(_ground_elevation_m > -9990.0) {
1058 if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 1.0)) {
1062 } else if(_pos.getElevationM() < (_ground_elevation_m + wheelOffset + 5.0)) {
1067 slope = _savedSlope;
1072 // Elev not determined
1073 slope = _savedSlope;
1078 slope = _savedSlope;
1084 // Try and track the extended centreline
1085 SetTrack(rwy.hdg - (0.2 * orthopos.x()));
1086 //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
1087 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+20.0+wheelOffset)) {
1088 DoGroundElev(); // Need to call it here expicitly on final since it's only called
1089 // for us in update(...) when the inAir flag is false.
1091 if(_pos.getElevationM() < (rwy.threshold_pos.getElevationM()+10.0+wheelOffset)) {
1094 if(_ground_elevation_m > -9990.0) {
1095 if((_ground_elevation_m + wheelOffset) > _pos.getElevationM()) {
1101 ClearTrack(); // Take over explicit track handling since AIPlane currently always banks when changing course
1103 } // else need a fallback position based on arpt elev in case ground elev determination fails?
1111 if(_ground_elevation_m > -9990.0) {
1112 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1117 // FIXME - differentiate between touch and go and full stops
1119 //cout << "Vel <= 15.0, circuitsToFly = " << circuitsToFly << endl;
1120 if(circuitsToFly <= 0) {
1121 //cout << "Calling ExitRunway..." << endl;
1122 ExitRunway(orthopos);
1125 //cout << "Taking off again..." << endl;
1136 // calculate ground speed and crab from the wind triangle
1137 double wind_angle = GetAngleDiff_deg(wind_from + 180, track);
1138 double wind_side = (wind_angle < 0) ? -1.0 : 1.0;
1140 double sine_of_crab = wind_speed / IAS * sin(fabs(wind_angle) * DCL_DEGREES_TO_RADIANS);
1141 if (sine_of_crab >= 1.0) {
1142 // The crosswind component is greater than the IAS,
1143 // we can't keep the aircraft on track.
1144 // Assume increased IAS such that it cancels lateral speed.
1145 // This is unrealistic, but not sure how the rest of the sim
1146 // would react to the aircraft going off course.
1147 // Should be a rare case anyway.
1148 crab = wind_side * 90.0;
1150 crab = asin(sine_of_crab) * DCL_RADIANS_TO_DEGREES * wind_side;
1152 vel = cos(wind_angle * DCL_DEGREES_TO_RADIANS) * wind_speed
1153 + cos(crab * DCL_DEGREES_TO_RADIANS) * IAS;
1154 } else { // on the ground - crab dosen't apply
1158 //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
1160 _hdg = track + crab;
1161 dclBoundHeading(_hdg);
1162 dist = vel * 0.514444 * dt;
1163 _pos = dclUpdatePosition(_pos, track, slope, dist);
1166 // Pattern direction is true for right, false for left
1167 void FGAILocalTraffic::CalculateSoD(double base_leg_pos, double downwind_leg_pos, bool pattern_direction) {
1168 // For now we'll ignore wind and hardwire the glide angle.
1169 double ga = 5.5; //degrees
1170 double pa = 1000.0 * SG_FEET_TO_METER; // pattern altitude in meters
1171 // FIXME - get glideslope angle and pattern altitude agl from airport details if available
1173 // For convienience, we'll have +ve versions of the input distances
1174 double blp = fabs(base_leg_pos);
1175 double dlp = fabs(downwind_leg_pos);
1177 //double turn_allowance = 150.0; // Approximate distance in meters that a 90deg corner is shortened by turned in a light plane.
1179 double stod = pa / tan(ga * DCL_DEGREES_TO_RADIANS); // distance in meters from touchdown point to start descent
1180 //cout << "Descent to start = " << stod << " meters out\n";
1181 if(stod < blp) { // Start descending on final
1183 SoD.y = stod * -1.0;
1185 } else if(stod < (blp + dlp)) { // Start descending on base leg
1188 SoD.x = (pattern_direction ? (stod - dlp) : (stod - dlp) * -1.0);
1189 } else { // Start descending on downwind leg
1191 SoD.x = (pattern_direction ? dlp : dlp * -1.0);
1192 SoD.y = (blp - (stod - (blp + dlp))) * -1.0;
1196 void FGAILocalTraffic::TransmitPatternPositionReport(void) {
1197 // airport name + "traffic" + airplane callsign + pattern direction + pattern leg + rwy + ?
1200 const string& apt_name = _controlled ? tower->get_name() : airportID;
1203 trns += " Traffic ";
1204 trns += plane.callsign;
1205 if(patternDirection == 1) {
1211 // We could probably get rid of this whole switch statement and just pass a string containing the leg from the FlyPattern function.
1212 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?
1214 // Fall through to CROSSWIND
1215 case CROSSWIND: // I don't think this case will be used here but it can't hurt to leave it in
1216 trns += "crosswind ";
1219 // Fall through to DOWNWIND
1221 trns += "downwind ";
1225 // Fall through to BASE
1230 // Fall through to FINAL
1231 case FINAL: // maybe this should include long/short final if appropriate?
1235 default: // Hopefully this won't be used
1239 trns += ConvertRwyNumToSpokenString(rwy.rwyID);
1243 // And add the airport name again
1246 pending_transmission = trns;
1247 ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute.
1251 // TODO - Really should enumerate these coded values.
1252 void FGAILocalTraffic::ProcessCallback(int code) {
1253 // 1 - Request Departure from ground
1254 // 2 - Report at hold short
1255 // 3 - Report runway vacated
1256 // 10 - report crosswind
1257 // 11 - report downwind
1259 // 13 - report final
1261 ground->RequestDeparture(plane, this);
1262 } else if(code == 2) {
1263 if (_controlled) tower->ContactAtHoldShort(plane, this, CIRCUIT);
1264 } else if(code == 3) {
1265 if (_controlled) tower->ReportRunwayVacated(plane.callsign);
1266 } else if(code == 11) {
1267 if (_controlled) tower->ReportDownwind(plane.callsign);
1268 } else if(code == 13) {
1269 if (_controlled) tower->ReportFinal(plane.callsign);
1270 } else if(code == 99) { // Flag this instance for deletion
1271 responseCounter = 0;
1273 SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " delete instance callback called.");
1277 void FGAILocalTraffic::ExitRunway(const SGVec3d& orthopos) {
1278 //cout << "In ExitRunway" << endl;
1279 //cout << "Runway ID is " << rwy.ID << endl;
1281 _clearedToLand = false;
1283 node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits?
1285 cout << "Node ID's of exits are ";
1286 for(unsigned int i=0; i<exitNodes.size(); ++i) {
1287 cout << exitNodes[i]->nodeID << ' ';
1291 if(exitNodes.size()) {
1292 //Find the next exit from orthopos.y
1294 double dist = 100000; //ie. longer than any runway in existance
1295 double backdist = 100000;
1296 node_array_iterator nItr = exitNodes.begin();
1297 node* rwyExit = *(exitNodes.begin());
1298 //int gateID; //This might want to be more persistant at some point
1299 while(nItr != exitNodes.end()) {
1300 d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable
1307 if(fabs(d) < backdist) {
1309 //TODO - need some logic here that if we don't get a forward exit we turn round and store the backwards one
1314 ourGate = ground->GetGateNode();
1315 if(ourGate == NULL) {
1316 // Implies no available gates - what shall we do?
1317 // For now just vanish the plane - possibly we can make this more elegant in the future
1318 SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
1319 //_aip.setVisible(false);
1320 //cout << "Setting visible false\n";
1321 operatingState = PARKED;
1324 path = ground->GetPath(rwyExit, ourGate);
1326 cout << "path returned was:" << endl;
1327 for(unsigned int i=0; i<path.size(); ++i) {
1328 switch(path[i]->struct_type) {
1330 cout << "NODE " << ((node*)(path[i]))->nodeID << endl;
1338 taxiState = TD_INBOUND;
1341 // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
1342 SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
1343 //if(airportID == "KRHV") cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
1344 // What shall we do - just remove the plane from sight?
1345 _aip.setVisible(false);
1347 //cout << "Setting visible false\n";
1348 //tower->ReportRunwayVacated(plane.callsign);
1349 string trns = "Clear of the runway ";
1350 trns += plane.callsign;
1351 pending_transmission = trns;
1353 operatingState = PARKED;
1357 // Set the class variable nextTaxiNode to the next node in the path
1358 // and update taxiPathPos, the class variable path iterator position
1359 // TODO - maybe should return error codes to the calling function if we fail here
1360 void FGAILocalTraffic::GetNextTaxiNode() {
1361 //cout << "GetNextTaxiNode called " << endl;
1362 //cout << "taxiPathPos = " << taxiPathPos << endl;
1363 ground_network_path_iterator pathItr = path.begin() + taxiPathPos;
1364 if(pathItr == path.end()) {
1365 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - no more nodes in path\n");
1367 if((*pathItr)->struct_type == NODE) {
1368 //cout << "ITS A NODE" << endl;
1369 //*pathItr = new node;
1370 nextTaxiNode = (node*)*pathItr;
1374 //cout << "ITS NOT A NODE" << endl;
1375 //The first item in found must have been an arc
1376 //Assume for now that it was straight
1379 if(pathItr == path.end()) {
1380 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - path ended with an arc\n");
1381 } else if((*pathItr)->struct_type == NODE) {
1382 nextTaxiNode = (node*)*pathItr;
1385 //OOPS - two non-nodes in a row - that shouldn't happen ATM
1386 SG_LOG(SG_ATC, SG_ALERT, "ERROR IN AILocalTraffic::GetNextTaxiNode - two non-nodes in sequence\n");
1392 // StartTaxi - set up the taxiing state - call only at the start of taxiing
1393 void FGAILocalTraffic::StartTaxi() {
1394 //cout << "StartTaxi called" << endl;
1395 operatingState = TAXIING;
1399 //Set the desired heading
1400 //Assume we are aiming for first node on path
1401 //Eventually we may need to consider the fact that we might start on a curved arc and
1402 //not be able to head directly for the first node.
1403 GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node!
1404 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1405 //cout << "First taxi heading is " << desiredTaxiHeading << endl;
1408 // speed in knots, headings in degrees, radius in meters.
1409 static double TaxiTurnTowardsHeading(double current_hdg, double desired_hdg, double speed, double radius, double dt) {
1410 // wrap heading - this prevents a logic bug where the plane would just go round in circles!!
1411 while(current_hdg < 0.0) {
1412 current_hdg += 360.0;
1414 while(current_hdg > 360.0) {
1415 current_hdg -= 360.0;
1417 if(fabs(current_hdg - desired_hdg) > 0.1) {
1418 // Which is the quickest direction to turn onto heading?
1419 if(desired_hdg > current_hdg) {
1420 if((desired_hdg - current_hdg) <= 180) {
1422 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1423 // TODO - check that increments are less than the delta that we check for the right direction
1424 // Probably need to reduce convergence speed as convergence is reached
1426 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1429 if((current_hdg - desired_hdg) <= 180) {
1431 current_hdg -= ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1432 // TODO - check that increments are less than the delta that we check for the right direction
1433 // Probably need to reduce convergence speed as convergence is reached
1435 current_hdg += ((speed * 0.514444 * dt) / (radius * DCL_PI)) * 180.0;
1439 return(current_hdg);
1442 void FGAILocalTraffic::Taxi(double dt) {
1443 //cout << "Taxi called" << endl;
1444 // Logic - if we are further away from next point than turn radius then head for it
1445 // If we have reached turning point then get next point and turn onto that heading
1446 // Look out for the finish!!
1448 desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
1450 bool lastNode = (taxiPathPos == path.size() ? true : false);
1452 //cout << "LAST NODE\n";
1455 // HACK ALERT! - for now we will taxi at constant speed for straights and turns
1457 // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
1458 double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
1459 //cout << "dist_to_go = " << dist_to_go << endl;
1460 if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
1461 // This might be more robust to outward paths starting with a gate if we check for either
1462 // last node or TD_INBOUND ?
1464 operatingState = PARKED;
1465 } else if(((dist_to_go > taxiTurnRadius) || (nextTaxiNode->type == GATE)) && (!liningUp)){
1466 // if the turn radius is r, and speed is s, then in a time dt we turn through
1467 // ((s.dt)/(PI.r)) x 180 degrees
1468 // or alternatively (s.dt)/r radians
1469 //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
1470 _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
1471 double vel = nominalTaxiSpeed;
1472 //cout << "vel = " << vel << endl;
1473 double dist = vel * 0.514444 * dt;
1474 //cout << "dist = " << dist << endl;
1475 double track = _hdg;
1476 //cout << "track = " << track << endl;
1478 _pos = dclUpdatePosition(_pos, track, slope, dist);
1479 //cout << "Updated position...\n";
1480 if(_ground_elevation_m > -9990) {
1481 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1482 } // else don't change the elev until we get a valid ground elev again!
1483 } else if(lastNode) {
1484 if(taxiState == TD_LINING_UP) {
1485 if((!liningUp) && (dist_to_go <= taxiTurnRadius)) {
1489 _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
1490 double vel = nominalTaxiSpeed;
1491 //cout << "vel = " << vel << endl;
1492 double dist = vel * 0.514444 * dt;
1493 //cout << "dist = " << dist << endl;
1494 double track = _hdg;
1495 //cout << "track = " << track << endl;
1497 _pos = dclUpdatePosition(_pos, track, slope, dist);
1498 //cout << "Updated position...\n";
1499 if(_ground_elevation_m > -9990) {
1500 _pos.setElevationM(_ground_elevation_m + wheelOffset);
1501 } // else don't change the elev until we get a valid ground elev again!
1502 if(fabs(_hdg - rwy.hdg) <= 1.0) {
1503 operatingState = IN_PATTERN;
1509 } else if(taxiState == TD_OUTBOUND) {
1510 // Pause awaiting further instructions
1511 // and for now assume we've reached the hold-short node
1512 holdingShort = true;
1513 } // else at the moment assume TD_INBOUND always ends in a gate in which case we can ignore it
1515 // Time to turn (we've already checked it's not the end we're heading for).
1516 // set the target node to be the next node which will prompt automatically turning onto
1517 // the right heading in the stuff above, with the usual provisos applied.
1519 // For now why not just recursively call this function?
1525 // Warning - ground elev determination is CPU intensive
1526 // Either this function or the logic of how often it is called
1527 // will almost certainly change.
1528 void FGAILocalTraffic::DoGroundElev() {
1529 // Only do the proper hitlist stuff if we are within visible range of the viewer.
1530 double visibility_meters = fgGetDouble("/environment/visibility-m");
1531 FGViewer* vw = globals->get_current_view();
1532 if(dclGetHorizontalSeparation(_pos, SGGeod::fromGeodM(vw->getPosition(), 0.0)) > visibility_meters) {
1533 _ground_elevation_m = aptElev;
1537 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1538 double range = 500.0;
1539 if (!globals->get_tile_mgr()->scenery_available(_aip.getPosition(), range)) {
1540 // Try to shedule tiles for that position.
1541 globals->get_tile_mgr()->update( _aip.getPosition(), range );
1544 // FIXME: make shure the pos.lat/pos.lon values are in degrees ...
1546 if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(_aip.getPosition(), 20000), alt, 0, _aip.getSceneGraph()))
1547 _ground_elevation_m = alt;