]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIFlightPlanCreate.cxx
Improve timing statistics
[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 "AIFlightPlan.hxx"
26 #include <simgear/math/sg_geodesy.hxx>
27 #include <simgear/props/props.hxx>
28 #include <simgear/props/props_io.hxx>
29
30 #include <Airports/simple.hxx>
31 #include <Airports/runways.hxx>
32 #include <Airports/dynamics.hxx>
33 #include "AIAircraft.hxx"
34 #include "performancedata.hxx"
35
36 #include <Environment/environment_mgr.hxx>
37 #include <Environment/environment.hxx>
38
39
40 /* FGAIFlightPlan::create()
41  * dynamically create a flight plan for AI traffic, based on data provided by the
42  * Traffic Manager, when reading a filed flightplan failes. (DT, 2004/07/10) 
43  *
44  * This is the top-level function, and the only one that is publicly available.
45  *
46  */
47
48
49 // Check lat/lon values during initialization;
50 void FGAIFlightPlan::create(FGAIAircraft * ac, FGAirport * dep,
51                             FGAirport * arr, int legNr, double alt,
52                             double speed, double latitude,
53                             double longitude, bool firstFlight,
54                             double radius, const string & fltType,
55                             const string & aircraftType,
56                             const string & airline, double distance)
57 {
58     int currWpt = wpt_iterator - waypoints.begin();
59     switch (legNr) {
60     case 1:
61         createPushBack(ac, firstFlight, dep, latitude, longitude,
62                        radius, fltType, aircraftType, airline);
63         break;
64     case 2:
65         createTakeoffTaxi(ac, firstFlight, dep, radius, fltType,
66                           aircraftType, airline);
67         break;
68     case 3:
69         createTakeOff(ac, firstFlight, dep, speed, fltType);
70         break;
71     case 4:
72         createClimb(ac, firstFlight, dep, speed, alt, fltType);
73         break;
74     case 5:
75         createCruise(ac, firstFlight, dep, arr, latitude, longitude, speed,
76                      alt, fltType);
77         break;
78     case 6:
79         createDescent(ac, arr, latitude, longitude, speed, alt, fltType,
80                       distance);
81         break;
82     case 7:
83         createLanding(ac, arr, fltType);
84         break;
85     case 8:
86         createLandingTaxi(ac, arr, radius, fltType, aircraftType, airline);
87         break;
88     case 9:
89         createParking(ac, arr, radius);
90         break;
91     default:
92         //exit(1);
93         SG_LOG(SG_INPUT, SG_ALERT,
94                "AIFlightPlan::create() attempting to create unknown leg"
95                " this is probably an internal program error");
96     }
97     wpt_iterator = waypoints.begin() + currWpt;
98     leg++;
99 }
100
101 FGAIFlightPlan::waypoint *
102     FGAIFlightPlan::createOnGround(FGAIAircraft * ac,
103                                    const std::string & aName,
104                                    const SGGeod & aPos, double aElev,
105                                    double aSpeed)
106 {
107     waypoint *wpt = new waypoint;
108     wpt->name = aName;
109     wpt->longitude = aPos.getLongitudeDeg();
110     wpt->latitude = aPos.getLatitudeDeg();
111     wpt->altitude = aElev;
112     wpt->speed = aSpeed;
113     wpt->crossat = -10000.1;
114     wpt->gear_down = true;
115     wpt->flaps_down = true;
116     wpt->finished = false;
117     wpt->on_ground = true;
118     wpt->routeIndex = 0;
119     return wpt;
120 }
121
122 FGAIFlightPlan::waypoint *
123     FGAIFlightPlan::createInAir(FGAIAircraft * ac,
124                                 const std::string & aName,
125                                 const SGGeod & aPos, double aElev,
126                                 double aSpeed)
127 {
128     waypoint *wpt = new waypoint;
129     wpt->name = aName;
130     wpt->longitude = aPos.getLongitudeDeg();
131     wpt->latitude = aPos.getLatitudeDeg();
132     wpt->altitude = aElev;
133     wpt->speed = aSpeed;
134     wpt->crossat = -10000.1;
135     wpt->gear_down = false;
136     wpt->flaps_down = false;
137     wpt->finished = false;
138     wpt->on_ground = false;
139     wpt->routeIndex = 0;
140     return wpt;
141 }
142
143 FGAIFlightPlan::waypoint *
144     FGAIFlightPlan::cloneWithPos(FGAIAircraft * ac, waypoint * aWpt,
145                                  const std::string & aName,
146                                  const SGGeod & aPos)
147 {
148     waypoint *wpt = new waypoint;
149     wpt->name = aName;
150     wpt->longitude = aPos.getLongitudeDeg();
151     wpt->latitude = aPos.getLatitudeDeg();
152
153     wpt->altitude = aWpt->altitude;
154     wpt->speed = aWpt->speed;
155     wpt->crossat = aWpt->crossat;
156     wpt->gear_down = aWpt->gear_down;
157     wpt->flaps_down = aWpt->flaps_down;
158     wpt->finished = aWpt->finished;
159     wpt->on_ground = aWpt->on_ground;
160     wpt->routeIndex = 0;
161
162     return wpt;
163 }
164
165 FGAIFlightPlan::waypoint * FGAIFlightPlan::clone(waypoint * aWpt)
166 {
167     waypoint *wpt = new waypoint;
168     wpt->name = aWpt->name;
169     wpt->longitude = aWpt->longitude;
170     wpt->latitude = aWpt->latitude;
171
172     wpt->altitude = aWpt->altitude;
173     wpt->speed = aWpt->speed;
174     wpt->crossat = aWpt->crossat;
175     wpt->gear_down = aWpt->gear_down;
176     wpt->flaps_down = aWpt->flaps_down;
177     wpt->finished = aWpt->finished;
178     wpt->on_ground = aWpt->on_ground;
179     wpt->routeIndex = 0;
180
181     return wpt;
182 }
183
184
185 void FGAIFlightPlan::createDefaultTakeoffTaxi(FGAIAircraft * ac,
186                                               FGAirport * aAirport,
187                                               FGRunway * aRunway)
188 {
189     SGGeod runwayTakeoff = aRunway->pointOnCenterline(5.0);
190     double airportElev = aAirport->getElevation();
191
192     waypoint *wpt;
193     wpt =
194         createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
195                        ac->getPerformance()->vTaxi());
196     waypoints.push_back(wpt);
197     wpt =
198         createOnGround(ac, "Runway Takeoff", runwayTakeoff, airportElev,
199                        ac->getPerformance()->vTaxi());
200     waypoints.push_back(wpt);
201 }
202
203 void FGAIFlightPlan::createTakeoffTaxi(FGAIAircraft * ac, bool firstFlight,
204                                        FGAirport * apt,
205                                        double radius,
206                                        const string & fltType,
207                                        const string & acType,
208                                        const string & airline)
209 {
210     double heading, lat, lon;
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         if (!(apt->getDynamics()->getAvailableParking(&lat, &lon,
217                                                       &heading, &gateId,
218                                                       radius, fltType,
219                                                       acType, airline))) {
220             SG_LOG(SG_INPUT, 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     rwy = apt->getRunwayByIdent(activeRunway);
238     SGGeod runwayTakeoff = rwy->pointOnCenterline(5.0);
239
240     FGGroundNetwork *gn = apt->getDynamics()->getGroundNetwork();
241     if (!gn->exists()) {
242         createDefaultTakeoffTaxi(ac, apt, rwy);
243         return;
244     }
245
246     intVec ids;
247     int runwayId = gn->findNearestNode(runwayTakeoff);
248
249     // A negative gateId indicates an overflow parking, use a
250     // fallback mechanism for this. 
251     // Starting from gate 0 in this case is a bit of a hack
252     // which requires a more proper solution later on.
253     delete taxiRoute;
254     taxiRoute = new FGTaxiRoute;
255
256     // Determine which node to start from.
257     int node = 0;
258     // Find out which node to start from
259     FGParking *park = apt->getDynamics()->getParking(gateId);
260     if (park) {
261         node = park->getPushBackPoint();
262     }
263
264     if (node == -1) {
265         node = gateId;
266     }
267     // HAndle case where parking doens't have a node
268     if ((node == 0) && park) {
269         if (firstFlight) {
270             node = gateId;
271         } else {
272             node = lastNodeVisited;
273         }
274     }
275
276     *taxiRoute = gn->findShortestRoute(node, runwayId);
277     intVecIterator i;
278
279     if (taxiRoute->empty()) {
280         createDefaultTakeoffTaxi(ac, apt, rwy);
281         return;
282     }
283
284     taxiRoute->first();
285     //bool isPushBackPoint = false;
286     if (firstFlight) {
287         // If this is called during initialization, randomly
288         // skip a number of waypoints to get a more realistic
289         // taxi situation.
290         int nrWaypointsToSkip = rand() % taxiRoute->size();
291         // but make sure we always keep two active waypoints
292         // to prevent a segmentation fault
293         for (int i = 0; i < nrWaypointsToSkip - 3; i++) {
294             taxiRoute->next(&node);
295         }
296         apt->getDynamics()->releaseParking(gateId);
297     } else {
298         if (taxiRoute->size() > 1) {
299             taxiRoute->next(&node);     // chop off the first waypoint, because that is already the last of the pushback route
300         }
301     }
302
303     // push each node on the taxi route as a waypoint
304     int route;
305     while (taxiRoute->next(&node, &route)) {
306         char buffer[10];
307         snprintf(buffer, 10, "%d", node);
308         FGTaxiNode *tn =
309             apt->getDynamics()->getGroundNetwork()->findNode(node);
310         waypoint *wpt =
311             createOnGround(ac, buffer, tn->getGeod(), apt->getElevation(),
312                            ac->getPerformance()->vTaxi());
313         wpt->routeIndex = route;
314         waypoints.push_back(wpt);
315     }
316 }
317
318 void FGAIFlightPlan::createDefaultLandingTaxi(FGAIAircraft * ac,
319                                               FGAirport * aAirport)
320 {
321     SGGeod lastWptPos =
322         SGGeod::fromDeg(waypoints.back()->longitude,
323                         waypoints.back()->latitude);
324     double airportElev = aAirport->getElevation();
325
326     waypoint *wpt;
327     wpt =
328         createOnGround(ac, "Runway Exit", lastWptPos, airportElev,
329                        ac->getPerformance()->vTaxi());
330     waypoints.push_back(wpt);
331     wpt =
332         createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
333                        ac->getPerformance()->vTaxi());
334     waypoints.push_back(wpt);
335
336     double heading, lat, lon;
337     aAirport->getDynamics()->getParking(gateId, &lat, &lon, &heading);
338     wpt =
339         createOnGround(ac, "END", SGGeod::fromDeg(lon, lat), airportElev,
340                        ac->getPerformance()->vTaxi());
341     waypoints.push_back(wpt);
342 }
343
344 void FGAIFlightPlan::createLandingTaxi(FGAIAircraft * ac, FGAirport * apt,
345                                        double radius,
346                                        const string & fltType,
347                                        const string & acType,
348                                        const string & airline)
349 {
350     double heading, lat, lon;
351     apt->getDynamics()->getAvailableParking(&lat, &lon, &heading,
352                                             &gateId, radius, fltType,
353                                             acType, airline);
354
355     SGGeod lastWptPos =
356         SGGeod::fromDeg(waypoints.back()->longitude,
357                         waypoints.back()->latitude);
358     FGGroundNetwork *gn = apt->getDynamics()->getGroundNetwork();
359
360     // Find a route from runway end to parking/gate.
361     if (!gn->exists()) {
362         createDefaultLandingTaxi(ac, apt);
363         return;
364     }
365
366     intVec ids;
367     int runwayId = gn->findNearestNode(lastWptPos);
368     // A negative gateId indicates an overflow parking, use a
369     // fallback mechanism for this. 
370     // Starting from gate 0 is a bit of a hack...
371     //FGTaxiRoute route;
372     delete taxiRoute;
373     taxiRoute = new FGTaxiRoute;
374     if (gateId >= 0)
375         *taxiRoute = gn->findShortestRoute(runwayId, gateId);
376     else
377         *taxiRoute = gn->findShortestRoute(runwayId, 0);
378     intVecIterator i;
379
380     if (taxiRoute->empty()) {
381         createDefaultLandingTaxi(ac, apt);
382         return;
383     }
384
385     int node;
386     taxiRoute->first();
387     int size = taxiRoute->size();
388     // Omit the last two waypoints, as 
389     // those are created by createParking()
390     int route;
391     for (int i = 0; i < size - 2; i++) {
392         taxiRoute->next(&node, &route);
393         char buffer[10];
394         snprintf(buffer, 10, "%d", node);
395         FGTaxiNode *tn = gn->findNode(node);
396         waypoint *wpt =
397             createOnGround(ac, buffer, tn->getGeod(), apt->getElevation(),
398                            ac->getPerformance()->vTaxi());
399         wpt->routeIndex = route;
400         waypoints.push_back(wpt);
401     }
402 }
403
404 /*******************************************************************
405  * CreateTakeOff 
406  * A note on units: 
407  *  - Speed -> knots -> nm/hour
408  *  - distance along runway =-> meters 
409  *  - accel / decel -> is given as knots/hour, but this is highly questionable:
410  *  for a jet_transport performance class, a accel / decel rate of 5 / 2 is 
411  *  given respectively. According to performance data.cxx, a value of kts / second seems
412  *  more likely however. 
413  * 
414  ******************************************************************/
415 void FGAIFlightPlan::createTakeOff(FGAIAircraft * ac, bool firstFlight,
416                                    FGAirport * apt, double speed,
417                                    const string & fltType)
418 {
419     double accel = ac->getPerformance()->acceleration();
420     double vTaxi = ac->getPerformance()->vTaxi();
421     double vRotate = ac->getPerformance()->vRotate();
422     double vTakeoff = ac->getPerformance()->vTakeoff();
423     //double vClimb = ac->getPerformance()->vClimb();
424
425     double accelMetric = (accel * SG_NM_TO_METER) / 3600;
426     double vTaxiMetric = (vTaxi * SG_NM_TO_METER) / 3600;
427     double vRotateMetric = (vRotate * SG_NM_TO_METER) / 3600;
428     double vTakeoffMetric = (vTakeoff * SG_NM_TO_METER) / 3600;
429     //double vClimbMetric = (vClimb * SG_NM_TO_METER) / 3600;
430     // Acceleration = dV / dT
431     // Acceleration X dT = dV
432     // dT = dT / Acceleration
433     //d = (Vf^2 - Vo^2) / (2*a)
434     //double accelTime = (vRotate - vTaxi) / accel;
435     //cerr << "Using " << accelTime << " as total acceleration time" << endl;
436     double accelDistance =
437         (vRotateMetric * vRotateMetric -
438          vTaxiMetric * vTaxiMetric) / (2 * accelMetric);
439     //cerr << "Using " << accelDistance << " " << accelMetric << " " << vRotateMetric << endl;
440     waypoint *wpt;
441     // Get the current active runway, based on code from David Luff
442     // This should actually be unified and extended to include 
443     // Preferential runway use schema's 
444     // NOTE: DT (2009-01-18: IIRC, this is currently already the case, 
445     // because the getActive runway function takes care of that.
446     if (firstFlight) {
447         string rwyClass = getRunwayClassFromTrafficType(fltType);
448         double heading = ac->getTrafficRef()->getCourse();
449         apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
450                                             heading);
451         rwy = apt->getRunwayByIdent(activeRunway);
452     }
453
454     double airportElev = apt->getElevation();
455     // Acceleration point, 105 meters into the runway,
456     SGGeod accelPoint = rwy->pointOnCenterline(105.0);
457     wpt = createOnGround(ac, "accel", accelPoint, airportElev, vRotate);
458     waypoints.push_back(wpt);
459
460
461     accelDistance =
462         (vTakeoffMetric * vTakeoffMetric -
463          vTaxiMetric * vTaxiMetric) / (2 * accelMetric);
464     //cerr << "Using " << accelDistance << " " << accelMetric << " " << vTakeoffMetric << endl;
465     accelPoint = rwy->pointOnCenterline(105.0 + accelDistance);
466     wpt = createOnGround(ac, "rotate", accelPoint, airportElev, vTakeoff);
467     waypoints.push_back(wpt);
468
469     accelDistance =
470         ((vTakeoffMetric * 1.1) * (vTakeoffMetric * 1.1) -
471          vTaxiMetric * vTaxiMetric) / (2 * accelMetric);
472     //cerr << "Using " << accelDistance << " " << accelMetric << " " << vTakeoffMetric << endl;
473     accelPoint = rwy->pointOnCenterline(105.0 + accelDistance);
474     wpt =
475         createOnGround(ac, "rotate", accelPoint, airportElev + 1000,
476                        vTakeoff * 1.1);
477     wpt->on_ground = false;
478     waypoints.push_back(wpt);
479
480     wpt = cloneWithPos(ac, wpt, "3000 ft", rwy->end());
481     wpt->altitude = airportElev + 3000;
482     waypoints.push_back(wpt);
483
484     // Finally, add two more waypoints, so that aircraft will remain under
485     // Tower control until they have reached the 3000 ft climb point
486     SGGeod pt = rwy->pointOnCenterline(5000 + rwy->lengthM() * 0.5);
487     wpt = cloneWithPos(ac, wpt, "5000 ft", pt);
488     wpt->altitude = airportElev + 5000;
489     waypoints.push_back(wpt);
490 }
491
492 /*******************************************************************
493  * CreateClimb
494  * initialize the Aircraft at the parking location
495  ******************************************************************/
496 void FGAIFlightPlan::createClimb(FGAIAircraft * ac, bool firstFlight,
497                                  FGAirport * apt, double speed, double alt,
498                                  const string & fltType)
499 {
500     waypoint *wpt;
501 //  bool planLoaded = false;
502     string fPLName;
503     double vClimb = ac->getPerformance()->vClimb();
504
505     if (firstFlight) {
506         string rwyClass = getRunwayClassFromTrafficType(fltType);
507         double heading = ac->getTrafficRef()->getCourse();
508         apt->getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
509                                             heading);
510         rwy = apt->getRunwayByIdent(activeRunway);
511     }
512     if (sid) {
513         for (wpt_vector_iterator i = sid->getFirstWayPoint();
514              i != sid->getLastWayPoint(); i++) {
515             waypoints.push_back(clone(*(i)));
516             //cerr << " Cloning waypoint " << endl;
517         }
518     } else {
519         SGGeod climb1 = rwy->pointOnCenterline(10 * SG_NM_TO_METER);
520         wpt = createInAir(ac, "10000ft climb", climb1, vClimb, 10000);
521         wpt->gear_down = true;
522         wpt->flaps_down = true;
523         waypoints.push_back(wpt);
524
525         SGGeod climb2 = rwy->pointOnCenterline(20 * SG_NM_TO_METER);
526         wpt = cloneWithPos(ac, wpt, "18000ft climb", climb2);
527         wpt->altitude = 18000;
528         waypoints.push_back(wpt);
529     }
530 }
531
532
533
534 /*******************************************************************
535  * CreateDescent
536  * Generate a flight path from the last waypoint of the cruise to 
537  * the permission to land point
538  ******************************************************************/
539 void FGAIFlightPlan::createDescent(FGAIAircraft * ac, FGAirport * apt,
540                                    double latitude, double longitude,
541                                    double speed, double alt,
542                                    const string & fltType,
543                                    double requiredDistance)
544 {
545     bool reposition = false;
546     waypoint *wpt;
547     double vDescent = ac->getPerformance()->vDescent();
548     double vApproach = ac->getPerformance()->vApproach();
549
550
551     //Beginning of Descent 
552     string rwyClass = getRunwayClassFromTrafficType(fltType);
553     double heading = ac->getTrafficRef()->getCourse();
554     apt->getDynamics()->getActiveRunway(rwyClass, 2, activeRunway,
555                                         heading);
556     rwy = apt->getRunwayByIdent(activeRunway);
557
558
559
560     // Create a slow descent path that ends 250 lateral to the runway.
561     double initialTurnRadius = getTurnRadius(vDescent, true);
562     //double finalTurnRadius = getTurnRadius(vApproach, true);
563
564 // get length of the downwind leg for the intended runway
565     double distanceOut = apt->getDynamics()->getApproachController()->getRunway(rwy->name())->getApproachDistance();    //12 * SG_NM_TO_METER;
566     //time_t previousArrivalTime=  apt->getDynamics()->getApproachController()->getRunway(rwy->name())->getEstApproachTime();
567
568
569     SGGeod current = SGGeod::fromDegM(longitude, latitude, 0);
570     SGGeod initialTarget = rwy->pointOnCenterline(-distanceOut);
571     SGGeod refPoint = rwy->pointOnCenterline(0);
572     double distance = SGGeodesy::distanceM(current, initialTarget);
573     double azimuth = SGGeodesy::courseDeg(current, initialTarget);
574     double dummyAz2;
575
576     // To prevent absurdly steep approaches, compute the origin from where the approach should have started
577     SGGeod origin;
578
579     if (ac->getTrafficRef()->getCallSign() ==
580         fgGetString("/ai/track-callsign")) {
581         //cerr << "Reposition information: Actual distance " << distance << ". required distance " << requiredDistance << endl;
582         //exit(1);
583     }
584
585     if (distance < requiredDistance * 0.8) {
586         reposition = true;
587         SGGeodesy::direct(initialTarget, azimuth,
588                           -requiredDistance, origin, dummyAz2);
589
590         distance = SGGeodesy::distanceM(current, initialTarget);
591         azimuth = SGGeodesy::courseDeg(current, initialTarget);
592     } else {
593         origin = current;
594     }
595
596
597     double dAlt = alt - (apt->getElevation() + 2000);
598
599     double nPoints = 100;
600
601     char buffer[16];
602
603     // The descent path contains the following phases:
604     // 1) a linear glide path from the initial position to
605     // 2) a semi circle turn to final
606     // 3) approach
607
608     //cerr << "Phase 1: Linear Descent path to runway" << rwy->name() << endl;
609     // Create an initial destination point on a semicircle
610     //cerr << "lateral offset : " << lateralOffset << endl;
611     //cerr << "Distance       : " << distance      << endl;
612     //cerr << "Azimuth        : " << azimuth       << endl;
613     //cerr << "Initial Lateral point: " << lateralOffset << endl;
614     double lat = refPoint.getLatitudeDeg();
615     double lon = refPoint.getLongitudeDeg();
616     //cerr << "Reference point (" << lat << ", " << lon << ")." << endl;
617     lat = initialTarget.getLatitudeDeg();
618     lon = initialTarget.getLongitudeDeg();
619     //cerr << "Initial Target point (" << lat << ", " << lon << ")." << endl;
620
621     double ratio = initialTurnRadius / distance;
622     if (ratio > 1.0)
623         ratio = 1.0;
624     if (ratio < -1.0)
625         ratio = -1.0;
626
627     double newHeading = asin(ratio) * SG_RADIANS_TO_DEGREES;
628     double newDistance =
629         cos(newHeading * SG_DEGREES_TO_RADIANS) * distance;
630     //cerr << "new distance " << newDistance << ". additional Heading " << newHeading << endl;
631     double side = azimuth - rwy->headingDeg();
632     double lateralOffset = initialTurnRadius;
633     if (side < 0)
634         side += 360;
635     if (side < 180) {
636         lateralOffset *= -1;
637     }
638     // Calculate the ETA at final, based on remaining distance, and approach speed.
639     // distance should really consist of flying time to terniary target, plus circle 
640     // but the distance to secondary target should work as a reasonable approximation
641     // aditionally add the amount of distance covered by making a turn of "side"
642     double turnDistance = (2 * M_PI * initialTurnRadius) * (side / 360.0);
643     time_t remaining =
644         (turnDistance + distance) / ((vDescent * SG_NM_TO_METER) / 3600.0);
645     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
646     //if (ac->getTrafficRef()->getCallSign() == fgGetString("/ai/track-callsign")) {
647     //     cerr << "   Arrival time estimation: turn angle " <<  side << ". Turn distance " << turnDistance << ". Linear distance " << distance << ". Time to go " << remaining << endl;
648     //     //exit(1);
649     //}
650
651     time_t eta = now + remaining;
652     //choose a distance to the runway such that it will take at least 60 seconds more
653     // time to get there than the previous aircraft.
654     // Don't bother when aircraft need to be repositioned, because that marks the initialization phased...
655
656     time_t newEta;
657
658     if (reposition == false) {
659         newEta =
660             apt->getDynamics()->getApproachController()->getRunway(rwy->
661                                                                    name
662                                                                    ())->
663             requestTimeSlot(eta);
664     } else {
665         newEta = eta;
666     }
667     //if ((eta < (previousArrivalTime+60)) && (reposition == false)) {
668     arrivalTime = newEta;
669     time_t additionalTimeNeeded = newEta - eta;
670     double distanceCovered =
671         ((vApproach * SG_NM_TO_METER) / 3600.0) * additionalTimeNeeded;
672     distanceOut += distanceCovered;
673     //apt->getDynamics()->getApproachController()->getRunway(rwy->name())->setEstApproachTime(eta+additionalTimeNeeded);
674     //cerr << "Adding additional distance: " << distanceCovered << " to allow " << additionalTimeNeeded << " seconds of flying time" << endl << endl;
675     //} else {
676     //apt->getDynamics()->getApproachController()->getRunway(rwy->name())->setEstApproachTime(eta);
677     //}
678     //cerr << "Timing information : Previous eta: " << previousArrivalTime << ". Current ETA : " << eta << endl;
679
680     SGGeod secondaryTarget =
681         rwy->pointOffCenterline(-distanceOut, lateralOffset);
682     initialTarget = rwy->pointOnCenterline(-distanceOut);
683     distance = SGGeodesy::distanceM(origin, secondaryTarget);
684     azimuth = SGGeodesy::courseDeg(origin, secondaryTarget);
685
686
687     lat = secondaryTarget.getLatitudeDeg();
688     lon = secondaryTarget.getLongitudeDeg();
689     //cerr << "Secondary Target point (" << lat << ", " << lon << ")." << endl;
690     //cerr << "Distance       : " << distance      << endl;
691     //cerr << "Azimuth        : " << azimuth       << endl;
692
693
694     ratio = initialTurnRadius / distance;
695     if (ratio > 1.0)
696         ratio = 1.0;
697     if (ratio < -1.0)
698         ratio = -1.0;
699     newHeading = asin(ratio) * SG_RADIANS_TO_DEGREES;
700     newDistance = cos(newHeading * SG_DEGREES_TO_RADIANS) * distance;
701     //cerr << "new distance realative to secondary target: " << newDistance << ". additional Heading " << newHeading << endl;
702     if (side < 180) {
703         azimuth += newHeading;
704     } else {
705         azimuth -= newHeading;
706     }
707
708     SGGeod tertiaryTarget;
709     SGGeodesy::direct(origin, azimuth,
710                       newDistance, tertiaryTarget, dummyAz2);
711
712     lat = tertiaryTarget.getLatitudeDeg();
713     lon = tertiaryTarget.getLongitudeDeg();
714     //cerr << "tertiary Target point (" << lat << ", " << lon << ")." << endl;
715
716
717     for (int i = 1; i < nPoints; i++) {
718         SGGeod result;
719         double currentDist = i * (newDistance / nPoints);
720         double currentAltitude = alt - (i * (dAlt / nPoints));
721         SGGeodesy::direct(origin, azimuth, currentDist, result, dummyAz2);
722         snprintf(buffer, 16, "descent%03d", i);
723         wpt = createInAir(ac, buffer, result, currentAltitude, vDescent);
724         wpt->crossat = currentAltitude;
725         wpt->trackLength = (newDistance / nPoints);
726         waypoints.push_back(wpt);
727         //cerr << "Track Length : " << wpt->trackLength;
728         //cerr << "  Position : " << result.getLatitudeDeg() << " " << result.getLongitudeDeg() << " " << currentAltitude << endl;
729     }
730
731     //cerr << "Phase 2: Circle " << endl;
732     double initialAzimuth =
733         SGGeodesy::courseDeg(secondaryTarget, tertiaryTarget);
734     double finalAzimuth =
735         SGGeodesy::courseDeg(secondaryTarget, initialTarget);
736
737     //cerr << "Angles from secondary target: " << initialAzimuth << " " << finalAzimuth << endl;
738     int increment, startval, endval;
739     // circle right around secondary target if orig of position is to the right of the runway
740     // i.e. use negative angles; else circle leftward and use postivi
741     if (side < 180) {
742         increment = -1;
743         startval = floor(initialAzimuth);
744         endval = ceil(finalAzimuth);
745         if (endval > startval) {
746             endval -= 360;
747         }
748     } else {
749         increment = 1;
750         startval = ceil(initialAzimuth);
751         endval = floor(finalAzimuth);
752         if (endval < startval) {
753             endval += 360;
754         }
755
756     }
757
758     //cerr << "creating circle between " << startval << " and " << endval << " using " << increment << endl;
759     double trackLength = (2 * M_PI * initialTurnRadius) / 360.0;
760     for (int i = startval; i != endval; i += increment) {
761         SGGeod result;
762         double currentAltitude = apt->getElevation() + 2000;
763         SGGeodesy::direct(secondaryTarget, i,
764                           initialTurnRadius, result, dummyAz2);
765         snprintf(buffer, 16, "turn%03d", i);
766         wpt = createInAir(ac, buffer, result, currentAltitude, vDescent);
767         wpt->crossat = currentAltitude;
768         wpt->trackLength = trackLength;
769         //cerr << "Track Length : " << wpt->trackLength;
770         waypoints.push_back(wpt);
771         //cerr << "  Position : " << result.getLatitudeDeg() << " " << result.getLongitudeDeg() << " " << currentAltitude << endl;
772     }
773
774
775     // The approach leg should bring the aircraft to approximately 4-6 out, after which the landing phase should take over. 
776     //cerr << "Phase 3: Approach" << endl;
777     distanceOut -= distanceCovered;
778     for (int i = 1; i < nPoints; i++) {
779         SGGeod result;
780         double currentDist = i * (distanceOut / nPoints);
781         double currentAltitude =
782             apt->getElevation() + 2000 - (i * 2000 / nPoints);
783         snprintf(buffer, 16, "final%03d", i);
784         result = rwy->pointOnCenterline((-distanceOut) + currentDist);
785         wpt = createInAir(ac, buffer, result, currentAltitude, vApproach);
786         wpt->crossat = currentAltitude;
787         wpt->trackLength = (distanceOut / nPoints);
788         // account for the extra distance due to an extended downwind leg
789         if (i == 1) {
790             wpt->trackLength += distanceCovered;
791         }
792         //cerr << "Track Length : " << wpt->trackLength;
793         waypoints.push_back(wpt);
794         //cerr << "  Position : " << result.getLatitudeDeg() << " " << result.getLongitudeDeg() << " " << currentAltitude << endl;
795     }
796
797     //cerr << "Done" << endl;
798
799     // Erase the two bogus BOD points: Note check for conflicts with scripted AI flightPlans
800     IncrementWaypoint(true);
801     IncrementWaypoint(true);
802
803     if (reposition) {
804         double tempDistance;
805         //double minDistance = HUGE_VAL;
806         string wptName;
807         tempDistance = SGGeodesy::distanceM(current, initialTarget);
808         time_t eta =
809             tempDistance / ((vDescent * SG_NM_TO_METER) / 3600.0) + now;
810         time_t newEta =
811             apt->getDynamics()->getApproachController()->getRunway(rwy->
812                                                                    name
813                                                                    ())->
814             requestTimeSlot(eta);
815         arrivalTime = newEta;
816         double newDistance =
817             ((vDescent * SG_NM_TO_METER) / 3600.0) * (newEta - now);
818         //cerr << "Repositioning information : eta" << eta << ". New ETA " << newEta << ". Diff = " << (newEta - eta) << ". Distance = " << tempDistance << ". New distance = " << newDistance << endl;
819         IncrementWaypoint(true);        // remove waypoint BOD2
820         while (checkTrackLength("final001") > newDistance) {
821             IncrementWaypoint(true);
822         }
823         //cerr << "Repositioning to waypoint " << (*waypoints.begin())->name << endl;
824         ac->resetPositionFromFlightPlan();
825     }
826
827
828 }
829
830 /*******************************************************************
831  * CreateLanding
832  * Create a flight path from the "permision to land" point (currently
833    hardcoded at 5000 meters from the threshold) to the threshold, at
834    a standard glide slope angle of 3 degrees. 
835  ******************************************************************/
836 void FGAIFlightPlan::createLanding(FGAIAircraft * ac, FGAirport * apt,
837                                    const string & fltType)
838 {
839     double vTouchdown = ac->getPerformance()->vTouchdown();
840     //double vTaxi = ac->getPerformance()->vTaxi();
841
842     //string rwyClass = getRunwayClassFromTrafficType(fltType);
843     //double heading = ac->getTrafficRef()->getCourse();
844     //apt->getDynamics()->getActiveRunway(rwyClass, 2, activeRunway, heading);
845     //rwy = apt->getRunwayByIdent(activeRunway);
846
847
848     waypoint *wpt;
849     double aptElev = apt->getElevation();
850
851     SGGeod coord;
852     char buffer[12];
853     for (int i = 1; i < 10; i++) {
854         snprintf(buffer, 12, "wpt%d", i);
855         coord = rwy->pointOnCenterline(rwy->lengthM() * (i / 10.0));
856         wpt = createOnGround(ac, buffer, coord, aptElev, (vTouchdown / i));
857         wpt->crossat = apt->getElevation();
858         waypoints.push_back(wpt);
859     }
860
861     /*
862        //Runway Threshold
863        wpt = createOnGround(ac, "Threshold", rwy->threshold(), aptElev, vTouchdown);
864        wpt->crossat = apt->getElevation();
865        waypoints.push_back(wpt); 
866
867        // Roll-out
868        wpt = createOnGround(ac, "Center", rwy->geod(), aptElev, vTaxi*2);
869        waypoints.push_back(wpt);
870
871        SGGeod rollOut = rwy->pointOnCenterline(rwy->lengthM() * 0.9);
872        wpt = createOnGround(ac, "Roll Out", rollOut, aptElev, vTaxi);
873        wpt->crossat   = apt->getElevation();
874        waypoints.push_back(wpt); 
875      */
876 }
877
878 /*******************************************************************
879  * CreateParking
880  * initialize the Aircraft at the parking location
881  ******************************************************************/
882 void FGAIFlightPlan::createParking(FGAIAircraft * ac, FGAirport * apt,
883                                    double radius)
884 {
885     waypoint *wpt;
886     double aptElev = apt->getElevation();
887     double lat = 0.0, lat2 = 0.0;
888     double lon = 0.0, lon2 = 0.0;
889     double az2 = 0.0;
890     double heading = 0.0;
891
892     double vTaxi = ac->getPerformance()->vTaxi();
893     double vTaxiReduced = vTaxi * (2.0 / 3.0);
894     apt->getDynamics()->getParking(gateId, &lat, &lon, &heading);
895     heading += 180.0;
896     if (heading > 360)
897         heading -= 360;
898     geo_direct_wgs_84(0, lat, lon, heading,
899                       2.2 * radius, &lat2, &lon2, &az2);
900     wpt =
901         createOnGround(ac, "taxiStart", SGGeod::fromDeg(lon2, lat2),
902                        aptElev, vTaxiReduced);
903     waypoints.push_back(wpt);
904
905     geo_direct_wgs_84(0, lat, lon, heading,
906                       0.1 * radius, &lat2, &lon2, &az2);
907
908     wpt =
909         createOnGround(ac, "taxiStart2", SGGeod::fromDeg(lon2, lat2),
910                        aptElev, vTaxiReduced);
911     waypoints.push_back(wpt);
912
913     wpt =
914         createOnGround(ac, "END", SGGeod::fromDeg(lon, lat), aptElev,
915                        vTaxiReduced);
916     waypoints.push_back(wpt);
917 }
918
919 /**
920  *
921  * @param fltType a string describing the type of
922  * traffic, normally used for gate assignments
923  * @return a converted string that gives the runway
924  * preference schedule to be used at aircraft having
925  * a preferential runway schedule implemented (i.e.
926  * having a rwyprefs.xml file
927  * 
928  * Currently valid traffic types for gate assignment:
929  * - gate (commercial gate)
930  * - cargo (commercial gargo),
931  * - ga (general aviation) ,
932  * - ul (ultralight),
933  * - mil-fighter (military - fighter),
934  * - mil-transport (military - transport)
935  *
936  * Valid runway classes:
937  * - com (commercial traffic: jetliners, passenger and cargo)
938  * - gen (general aviation)
939  * - ul (ultralight: I can imagine that these may share a runway with ga on some airports)
940  * - mil (all military traffic)
941  */
942 string FGAIFlightPlan::getRunwayClassFromTrafficType(string fltType)
943 {
944     if ((fltType == "gate") || (fltType == "cargo")) {
945         return string("com");
946     }
947     if (fltType == "ga") {
948         return string("gen");
949     }
950     if (fltType == "ul") {
951         return string("ul");
952     }
953     if ((fltType == "mil-fighter") || (fltType == "mil-transport")) {
954         return string("mil");
955     }
956     return string("com");
957 }
958
959
960 double FGAIFlightPlan::getTurnRadius(double speed, bool inAir)
961 {
962     double turn_radius;
963     if (inAir == false) {
964         turn_radius = ((360 / 30) * fabs(speed)) / (2 * M_PI);
965     } else {
966         turn_radius = 0.1911 * speed * speed;   // an estimate for 25 degrees bank
967     }
968     return turn_radius;
969 }