]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIFlightPlanCreate.cxx
795aceadbe603422838d413f1668e9772f29c07d
[flightgear.git] / src / AIModel / AIFlightPlanCreate.cxx
1 /******************************************************************************
2  * AIFlightPlanCreate.cxx
3  * Written by Durk Talsma, started May, 2004.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  *
19  **************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <cstdlib>
26
27 #include "AIFlightPlan.hxx"
28 #include <simgear/math/sg_geodesy.hxx>
29 #include <simgear/props/props.hxx>
30 #include <simgear/props/props_io.hxx>
31
32 #include <Airports/simple.hxx>
33 #include <Airports/runways.hxx>
34 #include <Airports/dynamics.hxx>
35 #include "AIAircraft.hxx"
36 #include "performancedata.hxx"
37
38 #include <Environment/environment_mgr.hxx>
39 #include <Environment/environment.hxx>
40 #include <FDM/LaRCsim/basic_aero.h>
41 #include <Navaids/navrecord.hxx>
42
43
44 /* FGAIFlightPlan::create()
45  * dynamically create a flight plan for AI traffic, based on data provided by the
46  * Traffic Manager, when reading a filed flightplan failes. (DT, 2004/07/10) 
47  *
48  * This is the top-level function, and the only one that is publicly available.
49  *
50  */
51
52
53 // Check lat/lon values during initialization;
54 bool FGAIFlightPlan::create(FGAIAircraft * ac, FGAirport * dep,
55                             FGAirport * arr, int legNr, double alt,
56                             double speed, double latitude,
57                             double longitude, bool firstFlight,
58                             double radius, const string & fltType,
59                             const string & aircraftType,
60                             const string & airline, double distance)
61 {
62     bool retVal = true;
63     int currWpt = wpt_iterator - waypoints.begin();
64     switch (legNr) {
65     case 1:
66         retVal = createPushBack(ac, firstFlight, dep,
67                                 radius, fltType, aircraftType, airline);
68         // Pregenerate the taxi leg.
69         //if (retVal) {
70         //    waypoints.back()->setName( waypoints.back()->getName() + string("legend")); 
71         //    retVal = createTakeoffTaxi(ac, false, dep, radius, fltType, aircraftType, airline);
72         //}
73         break;
74     case 2:
75         retVal =  createTakeoffTaxi(ac, firstFlight, dep, radius, fltType,
76                           aircraftType, airline);
77         break;
78     case 3:
79         retVal = createTakeOff(ac, firstFlight, dep, speed, fltType);
80         break;
81     case 4:
82         retVal = createClimb(ac, firstFlight, dep, arr, speed, alt, fltType);
83         break;
84     case 5:
85         retVal = createCruise(ac, firstFlight, dep, arr, latitude, longitude, speed,
86                      alt, fltType);
87         break;
88     case 6:
89         retVal = createDescent(ac, arr, latitude, longitude, speed, alt, fltType,
90                       distance);
91         break;
92     case 7:
93         retVal = createLanding(ac, arr, fltType);
94         break;
95     case 8:
96         retVal = createLandingTaxi(ac, arr, radius, fltType, aircraftType, airline);
97         break;
98     case 9:
99         retVal = createParking(ac, arr, radius);
100         break;
101     default:
102         //exit(1);
103         SG_LOG(SG_AI, SG_ALERT,
104                "AIFlightPlan::create() attempting to create unknown leg"
105                " this is probably an internal program error");
106     }
107     wpt_iterator = waypoints.begin() + currWpt;
108     //don't  increment leg right away, but only once we pass the actual last waypoint that was created.
109     // to do so, mark the last waypoint with a special status flag
110    if (retVal) {
111         waypoints.back()->setName( waypoints.back()->getName() + string("legend")); 
112         // "It's pronounced Leg-end" (Roger Glover (Deep Purple): come Hell or High Water DvD, 1993)
113    }
114
115
116     //leg++;
117     return retVal;
118 }
119
120 FGAIWaypoint * FGAIFlightPlan::createOnGround(FGAIAircraft * ac,
121                                    const std::string & aName,
122                                    const SGGeod & aPos, double aElev,
123                                    double aSpeed)
124 {
125     FGAIWaypoint *wpt  = new FGAIWaypoint;
126     wpt->setName       (aName                  );
127     wpt->setLongitude  (aPos.getLongitudeDeg() );
128     wpt->setLatitude   (aPos.getLatitudeDeg()  );
129     wpt->setAltitude   (aElev                  );
130     wpt->setSpeed      (aSpeed                 );
131     wpt->setCrossat    (-10000.1               );
132     wpt->setGear_down  (true                   );
133     wpt->setFlaps_down (true                   );
134     wpt->setFinished   (false                  );
135     wpt->setOn_ground  (true                   );
136     wpt->setRouteIndex (0                      );
137     return wpt;
138 }
139
140 FGAIWaypoint *    FGAIFlightPlan::createInAir(FGAIAircraft * ac,
141                                 const std::string & aName,
142                                 const SGGeod & aPos, double aElev,
143                                 double aSpeed)
144 {
145     FGAIWaypoint * wpt = createOnGround(ac, aName, aPos, aElev, aSpeed);
146     wpt->setGear_down  (false                   );
147     wpt->setFlaps_down (false                   );
148     wpt->setOn_ground  (false                   );
149     wpt->setCrossat    (aElev                   );
150     return wpt;
151 }
152
153 FGAIWaypoint * FGAIFlightPlan::clone(FGAIWaypoint * aWpt)
154 {
155     FGAIWaypoint *wpt = new FGAIWaypoint;
156     wpt->setName       ( aWpt->getName ()      );
157     wpt->setLongitude  ( aWpt->getLongitude()  );
158     wpt->setLatitude   ( aWpt->getLatitude()   );
159     wpt->setAltitude   ( aWpt->getAltitude()   );
160     wpt->setSpeed      ( aWpt->getSpeed()      );
161     wpt->setCrossat    ( aWpt->getCrossat()    );
162     wpt->setGear_down  ( aWpt->getGear_down()  );
163     wpt->setFlaps_down ( aWpt->getFlaps_down() );
164     wpt->setFinished   ( aWpt->isFinished()    );
165     wpt->setOn_ground  ( aWpt->getOn_ground()  );
166     wpt->setRouteIndex ( 0                     );
167
168     return wpt;
169 }
170
171
172 FGAIWaypoint * FGAIFlightPlan::cloneWithPos(FGAIAircraft * ac, FGAIWaypoint * aWpt,
173                                  const std::string & aName,
174                                  const SGGeod & aPos)
175 {
176     FGAIWaypoint *wpt = clone(aWpt);
177     wpt->setName       ( aName                   );
178     wpt->setLongitude  ( aPos.getLongitudeDeg () );
179     wpt->setLatitude   ( aPos.getLatitudeDeg  () );
180
181     return wpt;
182 }
183
184
185
186 void FGAIFlightPlan::createDefaultTakeoffTaxi(FGAIAircraft * ac,
187                                               FGAirport * aAirport,
188                                               FGRunway * aRunway)
189 {
190     SGGeod runwayTakeoff = aRunway->pointOnCenterline(5.0);
191     double airportElev = aAirport->getElevation();
192
193     FGAIWaypoint *wpt;
194     wpt =
195         createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
196                        ac->getPerformance()->vTaxi());
197     pushBackWaypoint(wpt);
198     wpt =
199         createOnGround(ac, "Runway Takeoff", runwayTakeoff, airportElev,
200                        ac->getPerformance()->vTaxi());
201     pushBackWaypoint(wpt);
202 }
203
204 bool FGAIFlightPlan::createTakeoffTaxi(FGAIAircraft * ac, bool firstFlight,
205                                        FGAirport * apt,
206                                        double radius,
207                                        const string & fltType,
208                                        const string & acType,
209                                        const string & airline)
210 {
211
212     // If this function is called during initialization,
213     // make sure we obtain a valid gate ID first
214     // and place the model at the location of the gate.
215     if (firstFlight)
216     {
217       gateId =  apt->getDynamics()->getAvailableParking(radius, fltType,
218                                                         acType, airline);
219       if (gateId < 0) {
220         SG_LOG(SG_AI, SG_WARN, "Could not find parking for a " <<
221                acType <<
222                " of flight type " << fltType <<
223                " of airline     " << airline <<
224                " at airport     " << apt->getId());
225       }
226     }
227
228     string rwyClass = getRunwayClassFromTrafficType(fltType);
229
230     // Only set this if it hasn't been set by ATC already.
231     if (activeRunway.empty()) {
232         //cerr << "Getting runway for " << ac->getTrafficRef()->getCallSign() << " at " << apt->getId() << endl;
233         double depHeading = ac->getTrafficRef()->getCourse();
234         apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
235                                             depHeading);
236     }
237     FGRunway * rwy = apt->getRunwayByIdent(activeRunway);
238     assert( rwy != NULL );
239     SGGeod runwayTakeoff = rwy->pointOnCenterline(5.0);
240
241     FGGroundNetwork *gn = apt->getDynamics()->getGroundNetwork();
242     if (!gn->exists()) {
243         createDefaultTakeoffTaxi(ac, apt, rwy);
244         return true;
245     }
246
247     intVec ids;
248     int runwayId = 0;
249     if (gn->getVersion() > 0) {
250         runwayId = gn->findNearestNodeOnRunway(runwayTakeoff);
251     } else {
252         runwayId = gn->findNearestNode(runwayTakeoff);
253     }
254
255     // A negative gateId indicates an overflow parking, use a
256     // fallback mechanism for this. 
257     // Starting from gate 0 in this case is a bit of a hack
258     // which requires a more proper solution later on.
259     delete taxiRoute;
260     taxiRoute = new FGTaxiRoute;
261
262     // Determine which node to start from.
263     int node = 0;
264     // Find out which node to start from
265     FGParking *park = apt->getDynamics()->getParking(gateId);
266     if (park) {
267         node = park->getPushBackPoint();
268     }
269
270     if (node == -1) {
271         node = gateId;
272     }
273     // HAndle case where parking doens't have a node
274     if ((node == 0) && park) {
275         if (firstFlight) {
276             node = gateId;
277         } else {
278             node = lastNodeVisited;
279         }
280     }
281
282     *taxiRoute = gn->findShortestRoute(node, runwayId);
283     intVecIterator i;
284
285     if (taxiRoute->empty()) {
286         createDefaultTakeoffTaxi(ac, apt, rwy);
287         return true;
288     }
289
290     taxiRoute->first();
291     //bool isPushBackPoint = false;
292     if (firstFlight) {
293         // If this is called during initialization, randomly
294         // skip a number of waypoints to get a more realistic
295         // taxi situation.
296         int nrWaypointsToSkip = rand() % taxiRoute->size();
297         // but make sure we always keep two active waypoints
298         // to prevent a segmentation fault
299         for (int i = 0; i < nrWaypointsToSkip - 3; i++) {
300             taxiRoute->next(&node);
301         }
302         apt->getDynamics()->releaseParking(gateId);
303     } else {
304         if (taxiRoute->size() > 1) {
305             taxiRoute->next(&node);     // chop off the first waypoint, because that is already the last of the pushback route
306         }
307     }
308
309     // push each node on the taxi route as a waypoint
310     int route;
311     //cerr << "Building taxi route" << endl;
312     while (taxiRoute->next(&node, &route)) {
313         char buffer[10];
314         snprintf(buffer, 10, "%d", node);
315         FGTaxiNode *tn =
316             apt->getDynamics()->getGroundNetwork()->findNode(node);
317         FGAIWaypoint *wpt =
318             createOnGround(ac, buffer, tn->geod(), apt->getElevation(),
319                            ac->getPerformance()->vTaxi());
320         wpt->setRouteIndex(route);
321         //cerr << "Nodes left " << taxiRoute->nodesLeft() << " ";
322         if (taxiRoute->nodesLeft() == 1) {
323             // Note that we actually have hold points in the ground network, but this is just an initial test.
324             //cerr << "Setting departurehold point: " << endl;
325             wpt->setName( wpt->getName() + string("DepartureHold"));
326         }
327         if (taxiRoute->nodesLeft() == 0) {
328             wpt->setName(wpt->getName() + string("Accel"));
329         }
330         pushBackWaypoint(wpt);
331     }
332     // Acceleration point, 105 meters into the runway,
333     SGGeod accelPoint = rwy->pointOnCenterline(105.0);
334     FGAIWaypoint *wpt = createOnGround(ac, "accel", accelPoint, apt->getElevation(), ac->getPerformance()->vRotate());
335     pushBackWaypoint(wpt);
336
337     //cerr << "[done]" << endl;
338     return true;
339 }
340
341 void FGAIFlightPlan::createDefaultLandingTaxi(FGAIAircraft * ac,
342                                               FGAirport * aAirport)
343 {
344     SGGeod lastWptPos =
345         SGGeod::fromDeg(waypoints.back()->getLongitude(),
346                         waypoints.back()->getLatitude());
347     double airportElev = aAirport->getElevation();
348
349     FGAIWaypoint *wpt;
350     wpt =
351         createOnGround(ac, "Runway Exit", lastWptPos, airportElev,
352                        ac->getPerformance()->vTaxi());
353     pushBackWaypoint(wpt);
354     wpt =
355         createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
356                        ac->getPerformance()->vTaxi());
357     pushBackWaypoint(wpt);
358
359     FGParking* parkPos = aAirport->getDynamics()->getParking(gateId);
360     if (parkPos) {
361         wpt = createOnGround(ac, "ENDtaxi", parkPos->geod(), airportElev,
362                          ac->getPerformance()->vTaxi());
363         pushBackWaypoint(wpt);
364     }
365 }
366
367 bool FGAIFlightPlan::createLandingTaxi(FGAIAircraft * ac, FGAirport * apt,
368                                        double radius,
369                                        const string & fltType,
370                                        const string & acType,
371                                        const string & airline)
372 {
373     gateId = apt->getDynamics()->getAvailableParking(radius, fltType,
374                                             acType, airline);
375
376     SGGeod lastWptPos =
377         SGGeod::fromDeg(waypoints.back()->getLongitude(),
378                         waypoints.back()->getLatitude());
379     FGGroundNetwork *gn = apt->getDynamics()->getGroundNetwork();
380
381     // Find a route from runway end to parking/gate.
382     if (!gn->exists()) {
383         createDefaultLandingTaxi(ac, apt);
384         return true;
385     }
386
387     intVec ids;
388     int runwayId = 0;
389     if (gn->getVersion() == 1) {
390         runwayId = gn->findNearestNodeOnRunway(lastWptPos);
391     } else {
392         runwayId = gn->findNearestNode(lastWptPos);
393     }
394     //cerr << "Using network node " << runwayId << endl;
395     // A negative gateId indicates an overflow parking, use a
396     // fallback mechanism for this. 
397     // Starting from gate 0 is a bit of a hack...
398     //FGTaxiRoute route;
399     delete taxiRoute;
400     taxiRoute = new FGTaxiRoute;
401     if (gateId >= 0)
402         *taxiRoute = gn->findShortestRoute(runwayId, gateId);
403     else
404         *taxiRoute = gn->findShortestRoute(runwayId, 0);
405     intVecIterator i;
406
407     if (taxiRoute->empty()) {
408         createDefaultLandingTaxi(ac, apt);
409         return true;
410     }
411
412     int node;
413     taxiRoute->first();
414     int size = taxiRoute->size();
415     // Omit the last two waypoints, as 
416     // those are created by createParking()
417     int route;
418     for (int i = 0; i < size - 2; i++) {
419         taxiRoute->next(&node, &route);
420         char buffer[10];
421         snprintf(buffer, 10, "%d", node);
422         FGTaxiNode *tn = gn->findNode(node);
423         FGAIWaypoint *wpt =
424             createOnGround(ac, buffer, tn->geod(), apt->getElevation(),
425                            ac->getPerformance()->vTaxi());
426         wpt->setRouteIndex(route);
427         pushBackWaypoint(wpt);
428     }
429     return true;
430 }
431
432 static double accelDistance(double v0, double v1, double accel)
433 {
434   double t = fabs(v1 - v0) / accel; // time in seconds to change velocity
435   // area under the v/t graph: (t * v0) + (dV / 2t) where (dV = v1 - v0)
436   return t * 0.5 * (v1 + v0); 
437 }
438
439 // find the horizontal distance to gain the specific altiude, holding
440 // a constant pitch angle. Used to compute distance based on standard FD/AP
441 // PITCH mode prior to VS or CLIMB engaging. Visually, we want to avoid
442 // a dip in the nose angle after rotation, during initial climb-out.
443 static double pitchDistance(double pitchAngleDeg, double altGainM)
444 {
445   return altGainM / tan(pitchAngleDeg * SG_DEGREES_TO_RADIANS);
446 }
447
448 /*******************************************************************
449  * CreateTakeOff 
450  * A note on units: 
451  *  - Speed -> knots -> nm/hour
452  *  - distance along runway =-> meters 
453  *  - accel / decel -> is given as knots/hour, but this is highly questionable:
454  *  for a jet_transport performance class, a accel / decel rate of 5 / 2 is 
455  *  given respectively. According to performance data.cxx, a value of kts / second seems
456  *  more likely however. 
457  * 
458  ******************************************************************/
459 bool FGAIFlightPlan::createTakeOff(FGAIAircraft * ac, bool firstFlight,
460                                    FGAirport * apt, double speed,
461                                    const string & fltType)
462 {
463     const double ACCEL_POINT = 105.0;
464   // climb-out angle in degrees. could move this to the perf-db but this
465   // value is pretty sane
466     const double INITIAL_PITCH_ANGLE = 10.0;
467   
468     double accel = ac->getPerformance()->acceleration();
469     double vTaxi = ac->getPerformance()->vTaxi();
470     double vRotate = ac->getPerformance()->vRotate();
471     double vTakeoff = ac->getPerformance()->vTakeoff();
472
473     double accelMetric = accel * SG_KT_TO_MPS;
474     double vTaxiMetric = vTaxi * SG_KT_TO_MPS;
475     double vRotateMetric = vRotate * SG_KT_TO_MPS;
476    
477     FGAIWaypoint *wpt;
478     // Get the current active runway, based on code from David Luff
479     // This should actually be unified and extended to include 
480     // Preferential runway use schema's 
481     // NOTE: DT (2009-01-18: IIRC, this is currently already the case, 
482     // because the getActive runway function takes care of that.
483     if (firstFlight) {
484         string rwyClass = getRunwayClassFromTrafficType(fltType);
485         double heading = ac->getTrafficRef()->getCourse();
486         apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
487                                             heading);
488     }
489   
490     FGRunway * rwy = apt->getRunwayByIdent(activeRunway);
491     assert( rwy != NULL );
492     double airportElev = apt->getElevation();
493     
494     double d = accelDistance(vTaxiMetric, vRotateMetric, accelMetric) + ACCEL_POINT;
495   
496     SGGeod accelPoint = rwy->pointOnCenterline(d);
497     wpt = createOnGround(ac, "rotate", accelPoint, airportElev, vTakeoff);
498     pushBackWaypoint(wpt);
499
500     double vRef = vTakeoff + 20; // climb-out at v2 + 20kts
501   
502     double gearUpDist = d + pitchDistance(INITIAL_PITCH_ANGLE, 400 * SG_FEET_TO_METER);
503     accelPoint = rwy->pointOnCenterline(gearUpDist);
504     
505     wpt = cloneWithPos(ac, wpt, "gear-up", accelPoint);
506     wpt->setSpeed(vRef);
507     wpt->setCrossat(airportElev + 400);
508     wpt->setOn_ground(false);
509     wpt->setGear_down(false);
510     pushBackWaypoint(wpt);
511   
512   // limit climbout speed to 240kts below 10000'
513     double vClimbBelow10000 = std::min(240.0, ac->getPerformance()->vClimb());
514   
515   // create two climb-out points. This is important becuase the first climb point will
516   // be a (sometimes large) turn towards the destination, and we don't want to
517   // commence that turn below 2000'
518     double climbOut = d + pitchDistance(INITIAL_PITCH_ANGLE, 2000 * SG_FEET_TO_METER);
519     accelPoint = rwy->pointOnCenterline(climbOut);
520     wpt = createInAir(ac, "2000'", accelPoint, airportElev + 2000, vClimbBelow10000);
521     pushBackWaypoint(wpt);
522   
523     climbOut = d + pitchDistance(INITIAL_PITCH_ANGLE, 2500 * SG_FEET_TO_METER);
524     accelPoint = rwy->pointOnCenterline(climbOut);
525     wpt = createInAir(ac, "2500'", accelPoint, airportElev + 2500, vClimbBelow10000);
526     pushBackWaypoint(wpt);
527
528     return true;
529 }
530
531 /*******************************************************************
532  * CreateClimb
533  * initialize the Aircraft at the parking location
534  ******************************************************************/
535 bool FGAIFlightPlan::createClimb(FGAIAircraft * ac, bool firstFlight,
536                                  FGAirport * apt, FGAirport* arrival,
537                                  double speed, double alt,
538                                  const string & fltType)
539 {
540     FGAIWaypoint *wpt;
541   //  string fPLName;
542     double vClimb = ac->getPerformance()->vClimb();
543   
544     if (firstFlight) {
545         string rwyClass = getRunwayClassFromTrafficType(fltType);
546         double heading = ac->getTrafficRef()->getCourse();
547         apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
548                                             heading);
549     }
550     if (sid) {
551         for (wpt_vector_iterator i = sid->getFirstWayPoint();
552              i != sid->getLastWayPoint(); i++) {
553             pushBackWaypoint(clone(*(i)));
554             //cerr << " Cloning waypoint " << endl;
555         }
556     } else {
557         FGRunway* runway = apt->getRunwayByIdent(activeRunway);
558         SGGeod cur = runway->end();
559         if (!waypoints.empty()) {
560           cur = waypoints.back()->getPos();
561         }
562       
563       // compute course towards destination
564         double course = SGGeodesy::courseDeg(cur, arrival->geod());
565       
566         SGGeod climb1 = SGGeodesy::direct(cur, course, 10 * SG_NM_TO_METER);
567         wpt = createInAir(ac, "10000ft climb", climb1, 10000, vClimb);
568         wpt->setGear_down(true);
569         wpt->setFlaps_down(true);
570         pushBackWaypoint(wpt);
571
572         SGGeod climb2 = SGGeodesy::direct(cur, course, 20 * SG_NM_TO_METER);
573         wpt = createInAir(ac, "18000ft climb", climb2, 18000, vClimb);
574         pushBackWaypoint(wpt);
575     }
576     return true;
577 }
578
579
580
581 /*******************************************************************
582  * CreateDescent
583  * Generate a flight path from the last waypoint of the cruise to 
584  * the permission to land point
585  ******************************************************************/
586 bool FGAIFlightPlan::createDescent(FGAIAircraft * ac, FGAirport * apt,
587                                    double latitude, double longitude,
588                                    double speed, double alt,
589                                    const string & fltType,
590                                    double requiredDistance)
591 {
592     bool reposition = false;
593     FGAIWaypoint *wpt;
594     double vDescent = ac->getPerformance()->vDescent();
595     double vApproach = ac->getPerformance()->vApproach();
596
597     //Beginning of Descent 
598     string rwyClass = getRunwayClassFromTrafficType(fltType);
599     double heading = ac->getTrafficRef()->getCourse();
600     apt->getDynamics()->getActiveRunway(rwyClass, 2, activeRunway,
601                                         heading);
602     FGRunway * rwy = apt->getRunwayByIdent(activeRunway);
603     assert( rwy != NULL );
604
605     // Create a slow descent path that ends 250 lateral to the runway.
606     double initialTurnRadius = getTurnRadius(vDescent, true);
607     //double finalTurnRadius = getTurnRadius(vApproach, true);
608
609 // get length of the downwind leg for the intended runway
610     double distanceOut = apt->getDynamics()->getApproachController()->getRunway(rwy->name())->getApproachDistance();    //12 * SG_NM_TO_METER;
611     //time_t previousArrivalTime=  apt->getDynamics()->getApproachController()->getRunway(rwy->name())->getEstApproachTime();
612
613
614     SGGeod current = SGGeod::fromDegM(longitude, latitude, 0);
615     SGGeod initialTarget = rwy->pointOnCenterline(-distanceOut);
616     SGGeod refPoint = rwy->pointOnCenterline(0);
617     double distance = SGGeodesy::distanceM(current, initialTarget);
618     double azimuth = SGGeodesy::courseDeg(current, initialTarget);
619     double dummyAz2;
620
621     // To prevent absurdly steep approaches, compute the origin from where the approach should have started
622     SGGeod origin;
623
624     if (ac->getTrafficRef()->getCallSign() ==
625         fgGetString("/ai/track-callsign")) {
626         //cerr << "Reposition information: Actual distance " << distance << ". required distance " << requiredDistance << endl;
627         //exit(1);
628     }
629
630     if (distance < requiredDistance * 0.8) {
631         reposition = true;
632         SGGeodesy::direct(initialTarget, azimuth,
633                           -requiredDistance, origin, dummyAz2);
634
635         distance = SGGeodesy::distanceM(current, initialTarget);
636         azimuth = SGGeodesy::courseDeg(current, initialTarget);
637     } else {
638         origin = current;
639     }
640
641
642     double dAlt = 0; //  = alt - (apt->getElevation() + 2000);
643     FGTaxiNode * tn = 0;
644     if (apt->getDynamics()->getGroundNetwork()) {
645         int node = apt->getDynamics()->getGroundNetwork()->findNearestNode(refPoint);
646         tn = apt->getDynamics()->getGroundNetwork()->findNode(node);
647     }
648     if (tn) {
649         dAlt = alt - ((tn->getElevationFt(apt->getElevation())) + 2000);
650     } else {
651         dAlt = alt - (apt->getElevation() + 2000);
652     }
653
654     double nPoints = 100;
655
656     char buffer[16];
657
658     // The descent path contains the following phases:
659     // 1) a linear glide path from the initial position to
660     // 2) a semi circle turn to final
661     // 3) approach
662
663     //cerr << "Phase 1: Linear Descent path to runway" << rwy->name() << endl;
664     // Create an initial destination point on a semicircle
665     //cerr << "lateral offset : " << lateralOffset << endl;
666     //cerr << "Distance       : " << distance      << endl;
667     //cerr << "Azimuth        : " << azimuth       << endl;
668     //cerr << "Initial Lateral point: " << lateralOffset << endl;
669 //    double lat = refPoint.getLatitudeDeg();
670 //    double lon = refPoint.getLongitudeDeg();
671     //cerr << "Reference point (" << lat << ", " << lon << ")." << endl;
672 //    lat = initialTarget.getLatitudeDeg();
673 //    lon = initialTarget.getLongitudeDeg();
674     //cerr << "Initial Target point (" << lat << ", " << lon << ")." << endl;
675
676     double ratio = initialTurnRadius / distance;
677     if (ratio > 1.0)
678         ratio = 1.0;
679     if (ratio < -1.0)
680         ratio = -1.0;
681
682     double newHeading = asin(ratio) * SG_RADIANS_TO_DEGREES;
683     double newDistance =
684         cos(newHeading * SG_DEGREES_TO_RADIANS) * distance;
685     //cerr << "new distance " << newDistance << ". additional Heading " << newHeading << endl;
686     double side = azimuth - rwy->headingDeg();
687     double lateralOffset = initialTurnRadius;
688     if (side < 0)
689         side += 360;
690     if (side < 180) {
691         lateralOffset *= -1;
692     }
693     // Calculate the ETA at final, based on remaining distance, and approach speed.
694     // distance should really consist of flying time to terniary target, plus circle 
695     // but the distance to secondary target should work as a reasonable approximation
696     // aditionally add the amount of distance covered by making a turn of "side"
697     double turnDistance = (2 * M_PI * initialTurnRadius) * (side / 360.0);
698     time_t remaining =
699         (turnDistance + distance) / ((vDescent * SG_NM_TO_METER) / 3600.0);
700     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
701     //if (ac->getTrafficRef()->getCallSign() == fgGetString("/ai/track-callsign")) {
702     //     cerr << "   Arrival time estimation: turn angle " <<  side << ". Turn distance " << turnDistance << ". Linear distance " << distance << ". Time to go " << remaining << endl;
703     //     //exit(1);
704     //}
705
706     time_t eta = now + remaining;
707     //choose a distance to the runway such that it will take at least 60 seconds more
708     // time to get there than the previous aircraft.
709     // Don't bother when aircraft need to be repositioned, because that marks the initialization phased...
710
711     time_t newEta;
712
713     if (reposition == false) {
714         newEta =
715             apt->getDynamics()->getApproachController()->getRunway(rwy->
716                                                                    name
717                                                                    ())->
718             requestTimeSlot(eta);
719     } else {
720         newEta = eta;
721     }
722     //if ((eta < (previousArrivalTime+60)) && (reposition == false)) {
723     arrivalTime = newEta;
724     time_t additionalTimeNeeded = newEta - eta;
725     double distanceCovered =
726         ((vApproach * SG_NM_TO_METER) / 3600.0) * additionalTimeNeeded;
727     distanceOut += distanceCovered;
728     //apt->getDynamics()->getApproachController()->getRunway(rwy->name())->setEstApproachTime(eta+additionalTimeNeeded);
729     //cerr << "Adding additional distance: " << distanceCovered << " to allow " << additionalTimeNeeded << " seconds of flying time" << endl << endl;
730     //} else {
731     //apt->getDynamics()->getApproachController()->getRunway(rwy->name())->setEstApproachTime(eta);
732     //}
733     //cerr << "Timing information : Previous eta: " << previousArrivalTime << ". Current ETA : " << eta << endl;
734
735     SGGeod secondaryTarget =
736         rwy->pointOffCenterline(-distanceOut, lateralOffset);
737     initialTarget = rwy->pointOnCenterline(-distanceOut);
738     distance = SGGeodesy::distanceM(origin, secondaryTarget);
739     azimuth = SGGeodesy::courseDeg(origin, secondaryTarget);
740
741
742 //    lat = secondaryTarget.getLatitudeDeg();
743 //    lon = secondaryTarget.getLongitudeDeg();
744     //cerr << "Secondary Target point (" << lat << ", " << lon << ")." << endl;
745     //cerr << "Distance       : " << distance      << endl;
746     //cerr << "Azimuth        : " << azimuth       << endl;
747
748
749     ratio = initialTurnRadius / distance;
750     if (ratio > 1.0)
751         ratio = 1.0;
752     if (ratio < -1.0)
753         ratio = -1.0;
754     newHeading = asin(ratio) * SG_RADIANS_TO_DEGREES;
755     newDistance = cos(newHeading * SG_DEGREES_TO_RADIANS) * distance;
756     //cerr << "new distance realative to secondary target: " << newDistance << ". additional Heading " << newHeading << endl;
757     if (side < 180) {
758         azimuth += newHeading;
759     } else {
760         azimuth -= newHeading;
761     }
762
763     SGGeod tertiaryTarget;
764     SGGeodesy::direct(origin, azimuth,
765                       newDistance, tertiaryTarget, dummyAz2);
766
767 //    lat = tertiaryTarget.getLatitudeDeg();
768 //    lon = tertiaryTarget.getLongitudeDeg();
769     //cerr << "tertiary Target point (" << lat << ", " << lon << ")." << endl;
770
771
772     for (int i = 1; i < nPoints; i++) {
773         SGGeod result;
774         double currentDist = i * (newDistance / nPoints);
775         double currentAltitude = alt - (i * (dAlt / nPoints));
776         SGGeodesy::direct(origin, azimuth, currentDist, result, dummyAz2);
777         snprintf(buffer, 16, "descent%03d", i);
778         wpt = createInAir(ac, buffer, result, currentAltitude, vDescent);
779         wpt->setCrossat(currentAltitude);
780         wpt->setTrackLength((newDistance / nPoints));
781         pushBackWaypoint(wpt);
782         //cerr << "Track Length : " << wpt->trackLength;
783         //cerr << "  Position : " << result.getLatitudeDeg() << " " << result.getLongitudeDeg() << " " << currentAltitude << endl;
784     }
785
786     //cerr << "Phase 2: Circle " << endl;
787     double initialAzimuth =
788         SGGeodesy::courseDeg(secondaryTarget, tertiaryTarget);
789     double finalAzimuth =
790         SGGeodesy::courseDeg(secondaryTarget, initialTarget);
791
792     //cerr << "Angles from secondary target: " << initialAzimuth << " " << finalAzimuth << endl;
793     int increment, startval, endval;
794     // circle right around secondary target if orig of position is to the right of the runway
795     // i.e. use negative angles; else circle leftward and use postivi
796     if (side < 180) {
797         increment = -1;
798         startval = floor(initialAzimuth);
799         endval = ceil(finalAzimuth);
800         if (endval > startval) {
801             endval -= 360;
802         }
803     } else {
804         increment = 1;
805         startval = ceil(initialAzimuth);
806         endval = floor(finalAzimuth);
807         if (endval < startval) {
808             endval += 360;
809         }
810
811     }
812
813     //cerr << "creating circle between " << startval << " and " << endval << " using " << increment << endl;
814     //FGTaxiNode * tn = apt->getDynamics()->getGroundNetwork()->findNearestNode(initialTarget);
815     double currentAltitude = 0;
816     if (tn) {
817         currentAltitude = (tn->getElevationFt(apt->getElevation())) + 2000;
818     } else {
819         currentAltitude = apt->getElevation() + 2000;
820     }
821         
822     double trackLength = (2 * M_PI * initialTurnRadius) / 360.0;
823     for (int i = startval; i != endval; i += increment) {
824         SGGeod result;
825         //double currentAltitude = apt->getElevation() + 2000;
826         
827         SGGeodesy::direct(secondaryTarget, i,
828                           initialTurnRadius, result, dummyAz2);
829         snprintf(buffer, 16, "turn%03d", i);
830         wpt = createInAir(ac, buffer, result, currentAltitude, vDescent);
831         wpt->setCrossat(currentAltitude);
832         wpt->setTrackLength(trackLength);
833         //cerr << "Track Length : " << wpt->trackLength;
834         pushBackWaypoint(wpt);
835         //cerr << "  Position : " << result.getLatitudeDeg() << " " << result.getLongitudeDeg() << " " << currentAltitude << endl;
836     }
837
838
839     // The approach leg should bring the aircraft to approximately 4-6 nm out, after which the landing phase should take over. 
840     //cerr << "Phase 3: Approach" << endl;
841     
842     //cerr << "Done" << endl;
843
844     // Erase the two bogus BOD points: Note check for conflicts with scripted AI flightPlans
845     IncrementWaypoint(true);
846     IncrementWaypoint(true);
847
848     if (reposition) {
849         double tempDistance;
850         //double minDistance = HUGE_VAL;
851         string wptName;
852         tempDistance = SGGeodesy::distanceM(current, initialTarget);
853         time_t eta =
854             tempDistance / ((vDescent * SG_NM_TO_METER) / 3600.0) + now;
855         time_t newEta =
856             apt->getDynamics()->getApproachController()->getRunway(rwy->
857                                                                    name
858                                                                    ())->
859             requestTimeSlot(eta);
860         arrivalTime = newEta;
861         double newDistance =
862             ((vDescent * SG_NM_TO_METER) / 3600.0) * (newEta - now);
863         //cerr << "Repositioning information : eta" << eta << ". New ETA " << newEta << ". Diff = " << (newEta - eta) << ". Distance = " << tempDistance << ". New distance = " << newDistance << endl;
864         IncrementWaypoint(true);        // remove waypoint BOD2
865         while (checkTrackLength("final001") > newDistance) {
866             IncrementWaypoint(true);
867         }
868         //cerr << "Repositioning to waypoint " << (*waypoints.begin())->name << endl;
869         ac->resetPositionFromFlightPlan();
870     }
871     waypoints[1]->setName( (waypoints[1]->getName() + string("legend"))); 
872     return true;
873 }
874
875 /**
876  * compute the distance along the centerline, to the ILS glideslope
877  * transmitter. Return -1 if there's no GS for the runway
878  */
879 static double runwayGlideslopeTouchdownDistance(FGRunway* rwy)
880 {
881   FGNavRecord* gs = rwy->glideslope();
882   if (!gs) {
883     return -1;
884   }
885   
886   SGVec3d runwayPosCart = SGVec3d::fromGeod(rwy->pointOnCenterline(0.0));
887   // compute a unit vector in ECF cartesian space, from the runway beginning to the end
888   SGVec3d runwayDirectionVec = normalize(SGVec3d::fromGeod(rwy->end()) - runwayPosCart);
889   SGVec3d gsTransmitterVec = gs->cart() - runwayPosCart;
890   
891 // project the gsTransmitterVec along the runwayDirctionVec to get out
892 // final value (in metres)
893   double dist = dot(runwayDirectionVec, gsTransmitterVec);
894   return dist;
895 }
896
897 /*******************************************************************
898  * CreateLanding
899  * Create a flight path from the "permision to land" point (currently
900    hardcoded at 5000 meters from the threshold) to the threshold, at
901    a standard glide slope angle of 3 degrees. 
902    Position : 50.0354 8.52592 384 364 11112
903  ******************************************************************/
904 bool FGAIFlightPlan::createLanding(FGAIAircraft * ac, FGAirport * apt,
905                                    const string & fltType)
906 {
907     double vTouchdown = ac->getPerformance()->vTouchdown();
908     double vTaxi      = ac->getPerformance()->vTaxi();
909     double decel     = ac->getPerformance()->deceleration() * 1.4;
910     double vApproach = ac->getPerformance()->vApproach();
911   
912     double vTouchdownMetric = (vTouchdown  * SG_NM_TO_METER) / 3600;
913     double vTaxiMetric      = (vTaxi       * SG_NM_TO_METER) / 3600;
914     double decelMetric      = (decel       * SG_NM_TO_METER) / 3600;
915
916     char buffer[12];
917     FGRunway * rwy = apt->getRunwayByIdent(activeRunway);
918     assert( rwy != NULL );
919     SGGeod threshold = rwy->threshold();
920     double currElev = threshold.getElevationFt();
921   
922     double touchdownDistance = runwayGlideslopeTouchdownDistance(rwy);
923     if (touchdownDistance < 0.0) {
924       double landingLength = rwy->lengthM() - (rwy->displacedThresholdM());
925       // touchdown 25% of the way along the landing area
926       touchdownDistance = rwy->displacedThresholdM() + (landingLength * 0.25);
927     }
928   
929     SGGeod coord;
930   // find glideslope entry point, 2000' above touchdown elevation
931     double glideslopeEntry = -((2000 * SG_FEET_TO_METER) / tan(3.0)) + touchdownDistance;
932     FGAIWaypoint *wpt = createInAir(ac, "Glideslope begin", rwy->pointOnCenterline(glideslopeEntry),
933                                   currElev + 2000, vApproach);
934     pushBackWaypoint(wpt);
935   
936   // deceleration point, 500' above touchdown elevation - slow from approach speed
937   // to touchdown speed
938     double decelPoint = -((500 * SG_FEET_TO_METER) / tan(3.0)) + touchdownDistance;
939     wpt = createInAir(ac, "500' decel", rwy->pointOnCenterline(decelPoint),
940                                   currElev + 2000, vTouchdown);
941     pushBackWaypoint(wpt);
942   
943   // compute elevation above the runway start, based on a 3-degree glideslope
944     double heightAboveRunwayStart = touchdownDistance *
945       tan(3.0 * SG_DEGREES_TO_RADIANS) * SG_METER_TO_FEET;
946     wpt = createInAir(ac, "CrossThreshold", rwy->begin(),
947                       heightAboveRunwayStart + currElev, vTouchdown);
948     pushBackWaypoint(wpt);
949   
950     double rolloutDistance = accelDistance(vTouchdownMetric, vTaxiMetric, decelMetric);
951   
952     int nPoints = 50;
953     for (int i = 1; i < nPoints; i++) {
954         snprintf(buffer, 12, "landing03%d", i);
955         double t = ((double) i) / nPoints;
956         coord = rwy->pointOnCenterline(touchdownDistance + (rolloutDistance * t));
957         double vel = (vTouchdownMetric * (1.0 - t)) + (vTaxiMetric * t);
958         wpt = createOnGround(ac, buffer, coord, currElev, vel);
959         wpt->setCrossat(currElev);
960         pushBackWaypoint(wpt);
961     }
962   
963     wpt->setSpeed(vTaxi);
964     double mindist = (1.1 * rolloutDistance) + touchdownDistance;
965
966     FGGroundNetwork *gn = apt->getDynamics()->getGroundNetwork();
967     if (!gn) {
968       return true;
969     }
970   
971     coord = rwy->pointOnCenterline(mindist);
972     int nodeId = 0;
973     if (gn->getVersion() > 0) {
974         nodeId = gn->findNearestNodeOnRunway(coord, rwy);
975     } else {
976         nodeId = gn->findNearestNode(coord);
977     }
978       
979     FGTaxiNode* tn = gn->findNode(nodeId);
980     if (tn) {
981         wpt = createOnGround(ac, buffer, tn->geod(), currElev, vTaxi);
982         pushBackWaypoint(wpt);
983     }
984
985     return true;
986 }
987
988 /*******************************************************************
989  * CreateParking
990  * initialize the Aircraft at the parking location
991  ******************************************************************/
992 bool FGAIFlightPlan::createParking(FGAIAircraft * ac, FGAirport * apt,
993                                    double radius)
994 {
995     FGAIWaypoint *wpt;
996     double aptElev = apt->getElevation();
997     double vTaxi = ac->getPerformance()->vTaxi();
998     double vTaxiReduced = vTaxi * (2.0 / 3.0);
999     FGParking* parking = apt->getDynamics()->getParking(gateId);
1000     if (!parking) {
1001       wpt = createOnGround(ac, "END-Parking", apt->geod(), aptElev,
1002                            vTaxiReduced);
1003       pushBackWaypoint(wpt);
1004
1005       return true;
1006     }
1007   
1008     double heading = SGMiscd::normalizePeriodic(0, 360, parking->getHeading() + 180.0);
1009     double az; // unused
1010     SGGeod pos;
1011   
1012     SGGeodesy::direct(parking->geod(), heading, 2.2 * parking->getRadius(),
1013                       pos, az);
1014   
1015     wpt = createOnGround(ac, "taxiStart", pos, aptElev, vTaxiReduced);
1016     pushBackWaypoint(wpt);
1017
1018     SGGeodesy::direct(parking->geod(), heading, 0.1 * parking->getRadius(),
1019                     pos, az);
1020     wpt = createOnGround(ac, "taxiStart2", pos, aptElev, vTaxiReduced);
1021     pushBackWaypoint(wpt);
1022
1023     wpt = createOnGround(ac, "END-Parking", parking->geod(), aptElev,
1024                        vTaxiReduced);
1025     pushBackWaypoint(wpt);
1026     return true;
1027 }
1028
1029 /**
1030  *
1031  * @param fltType a string describing the type of
1032  * traffic, normally used for gate assignments
1033  * @return a converted string that gives the runway
1034  * preference schedule to be used at aircraft having
1035  * a preferential runway schedule implemented (i.e.
1036  * having a rwyprefs.xml file
1037  * 
1038  * Currently valid traffic types for gate assignment:
1039  * - gate (commercial gate)
1040  * - cargo (commercial gargo),
1041  * - ga (general aviation) ,
1042  * - ul (ultralight),
1043  * - mil-fighter (military - fighter),
1044  * - mil-transport (military - transport)
1045  *
1046  * Valid runway classes:
1047  * - com (commercial traffic: jetliners, passenger and cargo)
1048  * - gen (general aviation)
1049  * - ul (ultralight: I can imagine that these may share a runway with ga on some airports)
1050  * - mil (all military traffic)
1051  */
1052 string FGAIFlightPlan::getRunwayClassFromTrafficType(string fltType)
1053 {
1054     if ((fltType == "gate") || (fltType == "cargo")) {
1055         return string("com");
1056     }
1057     if (fltType == "ga") {
1058         return string("gen");
1059     }
1060     if (fltType == "ul") {
1061         return string("ul");
1062     }
1063     if ((fltType == "mil-fighter") || (fltType == "mil-transport")) {
1064         return string("mil");
1065     }
1066     return string("com");
1067 }
1068
1069
1070 double FGAIFlightPlan::getTurnRadius(double speed, bool inAir)
1071 {
1072     double turn_radius;
1073     if (inAir == false) {
1074         turn_radius = ((360 / 30) * fabs(speed)) / (2 * M_PI);
1075     } else {
1076         turn_radius = 0.1911 * speed * speed;   // an estimate for 25 degrees bank
1077     }
1078     return turn_radius;
1079 }