FlightGear next
AIFlightPlanCreate.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIFlightPlanCreate.cxx
3 * SPDX-FileCopyrightText: Written by Durk Talsma, started May, 2004
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#ifdef HAVE_CONFIG_H
8#include <config.h>
9#endif
10
11#include <algorithm>
12
13#include <cstdio>
14#include <cstdlib>
15
16#include <simgear/math/sg_geodesy.hxx>
17#include <simgear/props/props.hxx>
18#include <simgear/props/props_io.hxx>
19#include <simgear/timing/sg_time.hxx>
20
21#include <Airports/airport.hxx>
22#include <Airports/dynamics.hxx>
24#include <Airports/runways.hxx>
25
28#include <Main/fg_props.hxx>
29#include <Navaids/navrecord.hxx>
30#include <Traffic/Schedule.hxx>
31
32#include "AIAircraft.hxx"
33#include "AIFlightPlan.hxx"
34#include "VectorMath.hxx"
35#include "performancedata.hxx"
36
37using std::string;
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 fails. (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;
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 if (legNr <= AILeg::TAKEOFF)
59 SG_LOG(SG_AI, SG_DEBUG, "Create Leg " << legNr << " " << (firstFlight ? "First" : "") << " Old Leg " << getLeg() << " At Airport : " << dep->getId());
60 else if (legNr <= AILeg::APPROACH)
61 SG_LOG(SG_AI, SG_DEBUG, "Create Leg " << legNr << " " << (firstFlight ? "First" : "") << " Old Leg " << getLeg() << " Departure Airport : " << dep->getId() << " Arrival Airport : " << arr->getId());
62 else
63 SG_LOG(SG_AI, SG_DEBUG, "Create Leg " << legNr << " " << (firstFlight ? "First" : "") << " Old Leg " << getLeg() << " At Airport : " << arr->getId());
64
65 bool retVal = true;
66 int currWpt = wpt_iterator - waypoints.begin();
67 switch (legNr) {
69 retVal = createPushBack(ac, firstFlight, dep,
70 radius, fltType, aircraftType, airline);
71 break;
73 retVal = createTakeoffTaxi(ac, firstFlight, dep, radius, fltType,
74 aircraftType, airline);
75 break;
76 case AILeg::TAKEOFF:
77 retVal = createTakeOff(ac, firstFlight, dep, SGGeod::fromDeg(longitude, latitude), speed, fltType);
78 break;
79 case AILeg::CLIMB:
80 retVal = createClimb(ac, firstFlight, dep, arr, speed, alt, fltType);
81 break;
82 case AILeg::CRUISE:
83 retVal = createCruise(ac, firstFlight, dep, arr, SGGeod::fromDeg(longitude, latitude), speed,
84 alt, fltType);
85 break;
86 case AILeg::APPROACH:
87 retVal = createDescent(ac, arr, SGGeod::fromDeg(longitude, latitude), speed, alt, fltType,
88 distance);
89 break;
91 retVal = createHold(ac, arr, SGGeod::fromDeg(longitude, latitude), speed, alt, fltType,
92 distance);
93 break;
94 case AILeg::LANDING:
95 retVal = createLanding(ac, arr, fltType);
96 break;
98 retVal = createLandingTaxi(ac, arr, radius, fltType, aircraftType, airline);
99 break;
100 case AILeg::PARKING:
101 retVal = createParking(ac, arr, radius);
102 break;
103 default:
104 //exit(1);
105 SG_LOG(SG_AI, SG_ALERT,
106 "AIFlightPlan::create() attempting to create unknown leg"
107 " this is probably an internal program error");
108 break;
109 }
110 //don't increment leg right away, but only once we pass the actual last waypoint that was created.
111 // to do so, mark the last waypoint with a special status flag
112 if (retVal) {
113 if (waypoints.empty()) {
114 SG_LOG(SG_AI, SG_WARN, ac->getCallSign() << "|AIFlightPlan::create() Leg " << legNr << " created empty waypoints.");
115 return retVal;
116 }
117 wpt_iterator = waypoints.begin() + currWpt;
118 if (waypoints.back()->getName().size() == 0) {
119 SG_LOG(SG_AI, SG_WARN, ac->getCallSign() << " Empty wpt name");
120 }
121 waypoints.back()->setName(waypoints.back()->getName() + string("legend"));
122 // "It's pronounced Leg-end" (Roger Glover (Deep Purple): come Hell or High Water DvD, 1993)
123 }
124 return retVal;
125}
126
127FGAIWaypoint* FGAIFlightPlan::createOnGround(FGAIAircraft* ac,
128 const std::string& aName,
129 const SGGeod& aPos, double aElev,
130 double aSpeed)
131{
132 FGAIWaypoint* wpt = new FGAIWaypoint;
133 wpt->setName(aName);
134 wpt->setLongitude(aPos.getLongitudeDeg());
135 wpt->setLatitude(aPos.getLatitudeDeg());
136 wpt->setAltitude(aElev);
137 wpt->setSpeed(aSpeed);
138 wpt->setCrossat(-10000.1);
139 wpt->setGear_down(true);
140 wpt->setFlaps(0.0f);
141 wpt->setSpoilers(0.0f);
142 wpt->setSpeedBrakes(0.0f);
143 wpt->setFinished(false);
144 wpt->setOn_ground(true);
145 wpt->setRouteIndex(0);
146
147 if (aSpeed > 0.0f) {
148 wpt->setGroundLights();
149 } else {
150 wpt->setPowerDownLights();
151 }
152
153 return wpt;
154}
155
156FGAIWaypoint* FGAIFlightPlan::createOnRunway(FGAIAircraft* ac,
157 const std::string& aName,
158 const SGGeod& aPos, double aElev,
159 double aSpeed)
160{
161 FGAIWaypoint* wpt = createOnGround(ac, aName, aPos, aElev, aSpeed);
162 wpt->setTakeOffLights();
163 return wpt;
164}
165
166void FGAIFlightPlan::createArc(FGAIAircraft* ac, const SGGeod& center, int startAngle,
167 int endAngle, int increment, int radius, double aElev, double altDiff, double aSpeed, const char* pattern)
168{
169 double trackSegmentLength = (2 * M_PI * radius) / 360.0;
170 double dummyAz2;
171 char buffer[20];
172
173 if (endAngle > startAngle && increment < 0) {
174 endAngle -= 360;
175 }
176 if (endAngle < startAngle && increment > 0) {
177 endAngle += 360;
178 }
179
180 int nPoints = fabs(fabs(endAngle - startAngle) / increment);
181 double currentAltitude = aElev;
182 double altDecrement = altDiff / nPoints;
183
184 for (int i = startAngle; !(endAngle <= i && i < endAngle + fabs(increment)); i += increment) {
185 if (fabs(i) > 720) {
186 SG_LOG(SG_AI, SG_WARN, "FGAIFlightPlan::createArc runaway " << startAngle << " " << endAngle << " " << increment);
187 break;
188 }
189 SGGeod result;
190 SGGeodesy::direct(center, i,
191 radius, result, dummyAz2);
192 snprintf(buffer, sizeof(buffer), pattern, i);
193 currentAltitude -= altDecrement;
194 FGAIWaypoint* wpt = createInAir(ac, buffer, result, currentAltitude, aSpeed);
195 wpt->setCrossat(currentAltitude);
196 wpt->setTrackLength(trackSegmentLength);
197 pushBackWaypoint(wpt);
198 }
199}
200
201void FGAIFlightPlan::createLine(FGAIAircraft* ac, const SGGeod& startPoint, double azimuth, double dist, double aElev, double dAlt, double vDescent, const char* pattern)
202{
203 double nPoints = dist / (vDescent * 4);
204 char buffer[20];
205 double distIncrement = (dist / nPoints);
206
207 for (int i = 1; i < nPoints; i++) {
208 double currentDist = i * distIncrement;
209 double currentAltitude = aElev - (i * (dAlt / nPoints));
210 SGGeod result = SGGeodesy::direct(startPoint, azimuth, currentDist);
211 snprintf(buffer, sizeof(buffer), pattern, i);
212 FGAIWaypoint* wpt = createInAir(ac, buffer, result, currentAltitude, vDescent);
213 wpt->setCrossat(currentAltitude);
214 wpt->setTrackLength((dist / nPoints));
215 pushBackWaypoint(wpt);
216 }
217}
218
219FGAIWaypoint* FGAIFlightPlan::createInAir(FGAIAircraft* ac,
220 const std::string& aName,
221 const SGGeod& aPos, double aElev,
222 double aSpeed)
223{
224 FGAIWaypoint* wpt = createOnGround(ac, aName, aPos, aElev, aSpeed);
225 wpt->setGear_down(false);
226 wpt->setFlaps(0.0f);
227 wpt->setSpoilers(0.0f);
228 wpt->setSpeedBrakes(0.0f);
229 wpt->setOn_ground(false);
230 wpt->setCrossat(aElev);
231
232 if (aPos.getElevationFt() < 10000.0f) {
233 wpt->setApproachLights();
234 } else {
235 wpt->setCruiseLights();
236 }
237 return wpt;
238}
239
240FGAIWaypoint* FGAIFlightPlan::clone(FGAIWaypoint* aWpt)
241{
242 FGAIWaypoint* wpt = new FGAIWaypoint;
243 wpt->setName(aWpt->getName());
244 wpt->setLongitude(aWpt->getLongitude());
245 wpt->setLatitude(aWpt->getLatitude());
246 wpt->setAltitude(aWpt->getAltitude());
247 wpt->setSpeed(aWpt->getSpeed());
248 wpt->setCrossat(aWpt->getCrossat());
249 wpt->setGear_down(aWpt->getGear_down());
250 wpt->setFlaps(aWpt->getFlaps());
251 wpt->setFinished(aWpt->isFinished());
252 wpt->setOn_ground(aWpt->getOn_ground());
253 wpt->setLandingLight(aWpt->getLandingLight());
254 wpt->setNavLight(aWpt->getNavLight());
255 wpt->setStrobeLight(aWpt->getStrobeLight());
256 wpt->setTaxiLight(aWpt->getTaxiLight());
257 wpt->setRouteIndex(0);
258
259 return wpt;
260}
261
262
263FGAIWaypoint* FGAIFlightPlan::cloneWithPos(FGAIAircraft* ac, FGAIWaypoint* aWpt,
264 const std::string& aName,
265 const SGGeod& aPos)
266{
267 FGAIWaypoint* wpt = clone(aWpt);
268 wpt->setName(aName);
269 wpt->setLongitude(aPos.getLongitudeDeg());
270 wpt->setLatitude(aPos.getLatitudeDeg());
271
272 return wpt;
273}
274
275
276void FGAIFlightPlan::createDefaultTakeoffTaxi(FGAIAircraft* ac,
277 FGAirport* aAirport,
278 FGRunway* aRunway)
279{
280 SGGeod runwayTakeoff = aRunway->pointOnCenterline(5.0);
281 double airportElev = aAirport->getElevation();
282
283 FGAIWaypoint* wpt;
284 wpt =
285 createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
286 ac->getPerformance()->vTaxi());
287 pushBackWaypoint(wpt);
288 wpt =
289 createOnRunway(ac, "Runway Takeoff", runwayTakeoff, airportElev,
290 ac->getPerformance()->vTaxi());
291 wpt->setFlaps(0.5f);
292 pushBackWaypoint(wpt);
293
294
295 // Acceleration point, 105 meters into the runway,
296 SGGeod accelPoint = aRunway->pointOnCenterline(105.0);
297 wpt = createOnRunway(ac, "Accel", accelPoint, airportElev,
298 ac->getPerformance()->vRotate());
299 pushBackWaypoint(wpt);
300}
301
305
306bool FGAIFlightPlan::createTakeoffTaxi(FGAIAircraft* ac, bool firstFlight,
307 FGAirport* apt,
308 double radius,
309 const string& fltType,
310 const string& acType,
311 const string& airline)
312{
313 int route;
314 // If this function is called during initialization,
315 // make sure we obtain a valid gate ID first
316 // and place the model at the location of the gate.
317 if (firstFlight && apt->getDynamics()->hasParkings()) {
318 gate = apt->getDynamics()->getAvailableParking(radius, fltType,
319 acType, airline);
320 if (!gate.isValid()) {
321 SG_LOG(SG_AI, SG_WARN, "Could not find parking for a " << acType << " of flight type " << fltType << " of airline " << airline << " at airport " << apt->getId());
322 }
323 }
324
325 const string& rwyClass = getRunwayClassFromTrafficType(fltType);
326
327 // Only set this if it hasn't been set by ATC already.
328 if (activeRunway.empty()) {
329 // cerr << "Getting runway for " << ac->getTrafficRef()->getCallSign() << " at " << apt->getId() << endl;
330 double depHeading = ac->getTrafficRef()->getCourse();
331 apt->getDynamics()->getActiveRunway(rwyClass, RunwayAction::TAKEOFF, activeRunway,
332 depHeading);
333 }
334 FGRunway* rwy = apt->getRunwayByIdent(activeRunway);
335 SG_LOG(SG_AI, SG_DEBUG, "Taxi to " << apt->getId() << "/" << activeRunway);
336 assert(rwy != NULL);
337 SGGeod runwayTakeoff = rwy->pointOnCenterlineDisplaced(5.0);
338
339 FGGroundNetwork* gn = apt->groundNetwork();
340 if (!gn->exists()) {
341 SG_LOG(SG_AI, SG_DEBUG, "No groundnet " << apt->getId() << " creating default taxi.");
342 createDefaultTakeoffTaxi(ac, apt, rwy);
343 return true;
344 }
345
346 FGTaxiNodeRef runwayNode;
347 if (gn->getVersion() > 0) {
348 runwayNode = gn->findNearestNodeOnRunwayEntry(runwayTakeoff);
349 } else {
350 runwayNode = gn->findNearestNode(runwayTakeoff);
351 }
352
353 // A negative gateId indicates an overflow parking, use a
354 // fallback mechanism for this.
355 // Starting from gate 0 in this case is a bit of a hack
356 // which requires a more proper solution later on.
357 // delete taxiRoute;
358 // taxiRoute = new FGTaxiRoute;
359
360 // Determine which node to start from.
361 FGTaxiNodeRef node;
362 // Find out which node to start from
363 FGParking* park = gate.parking();
364 if (park) {
365 node = park->getPushBackPoint();
366 if (node == 0) {
367 // Handle case where parking doesn't have a node
368 if (firstFlight) {
369 node = park;
370 } else if (lastNodeVisited) {
371 node = lastNodeVisited;
372 } else {
373 SG_LOG(SG_AI, SG_WARN, "Taxiroute could not be constructed no lastNodeVisited at " << (apt ? apt->getId() : "????") << gate.isValid());
374 }
375 }
376 } else {
377 SG_LOG(SG_AI, SG_WARN, "Taxiroute could not be constructed no parking." << (apt ? apt->getId() : "????"));
378 }
379
380 FGTaxiRoute taxiRoute;
381 if (runwayNode && node) {
382 taxiRoute = gn->findShortestRoute(node, runwayNode);
383 } else {
384 }
385
386 // This may happen with buggy ground networks
387 if (taxiRoute.size() <= 1) {
388 SG_LOG(SG_AI, SG_DEBUG, "Taxiroute too short " << apt->getId() << "creating default taxi.");
389 createDefaultTakeoffTaxi(ac, apt, rwy);
390 return true;
391 }
392
393 taxiRoute.first();
394 FGTaxiNodeRef skipNode;
395
396 //bool isPushBackPoint = false;
397 if (firstFlight) {
398 // If this is called during initialization, randomly
399 // skip a number of waypoints to get a more realistic
400 // taxi situation.
401 int nrWaypointsToSkip = rand() % taxiRoute.size();
402 // but make sure we always keep two active waypoints
403 // to prevent a segmentation fault
404 for (int i = 0; i < nrWaypointsToSkip - 3; i++) {
405 taxiRoute.next(skipNode, &route);
406 }
407
408 gate.release(); // free up our gate as required
409 } else {
410 if (taxiRoute.size() > 1) {
411 taxiRoute.next(skipNode, &route); // chop off the first waypoint, because that is already the last of the pushback route
412 }
413 }
414
415 // push each node on the taxi route as a waypoint
416
417 //cerr << "Building taxi route" << endl;
418
419 // Note that the line wpt->setRouteIndex was commented out by revision [afcdbd] 2012-01-01,
420 // which breaks the rendering functions.
421 // These can probably be generated on the fly however.
422 while (taxiRoute.next(node, &route)) {
423 char buffer[10];
424 snprintf(buffer, sizeof(buffer), "%d", node->getIndex());
425 FGAIWaypoint* wpt =
426 createOnGround(ac, buffer, node->geod(), apt->getElevation(),
427 ac->getPerformance()->vTaxi());
428 wpt->setRouteIndex(route);
429 //cerr << "Nodes left " << taxiRoute->nodesLeft() << " ";
430 if (taxiRoute.nodesLeft() == 1) {
431 // Note that we actually have hold points in the ground network, but this is just an initial test.
432 //cerr << "Setting departurehold point: " << endl;
433 wpt->setName(wpt->getName() + string("_DepartureHold"));
434 wpt->setFlaps(0.5f);
435 wpt->setTakeOffLights();
436 }
437 if (taxiRoute.nodesLeft() == 0) {
438 wpt->setName(wpt->getName() + string("_Accel"));
439 wpt->setTakeOffLights();
440 wpt->setFlaps(0.5f);
441 }
442 pushBackWaypoint(wpt);
443 }
444 double accell_point = 105.0;
445 // Acceleration point, 105 meters into the runway,
446 SGGeod entryPoint = waypoints.back()->getPos();
447 SGGeod runwayEnd = rwy->pointOnCenterlineDisplaced(0);
448 double distM = SGGeodesy::distanceM(entryPoint, runwayEnd);
449 if (distM > accell_point) {
450 SG_LOG(SG_AI, SG_BULK, "Distance down runway " << distM << " " << accell_point);
451 accell_point += distM;
452 }
453 SGGeod accelPoint = rwy->pointOnCenterlineDisplaced(accell_point);
454 FGAIWaypoint* wpt = createOnRunway(ac, "Accel", accelPoint, apt->getElevation(), ac->getPerformance()->vRotate());
455 wpt->setFlaps(0.5f);
456 pushBackWaypoint(wpt);
457
458 time_t now = globals->get_time_params()->get_cur_time();
459
460 arrivalTime = now + calcArrivalTimes();
461 //cerr << "[done]" << endl;
462 return true;
463}
464
465void FGAIFlightPlan::createDefaultLandingTaxi(FGAIAircraft* ac,
466 FGAirport* aAirport)
467{
468 SGGeod lastWptPos =
469 SGGeod::fromDeg(waypoints.back()->getLongitude(),
470 waypoints.back()->getLatitude());
471 double airportElev = aAirport->getElevation();
472
473 FGAIWaypoint* wpt;
474 wpt =
475 createOnGround(ac, "Runway Exit", lastWptPos, airportElev,
476 ac->getPerformance()->vTaxi());
477 pushBackWaypoint(wpt);
478 wpt =
479 createOnGround(ac, "Airport Center", aAirport->geod(), airportElev,
480 ac->getPerformance()->vTaxi());
481 pushBackWaypoint(wpt);
482
483 if (gate.isValid()) {
484 wpt = createOnGround(ac, "END-taxi", gate.parking()->geod(), airportElev,
485 ac->getPerformance()->vTaxi());
486 pushBackWaypoint(wpt);
487 }
488 time_t now = globals->get_time_params()->get_cur_time();
489
490 arrivalTime = now + calcArrivalTimes();
491}
492
493bool FGAIFlightPlan::createLandingTaxi(FGAIAircraft* ac,
494 FGAirport* apt,
495 double radius,
496 const string& fltType,
497 const string& acType,
498 const string& airline)
499{
500 int route;
501 gate = apt->getDynamics()->getAvailableParking(radius, fltType,
502 acType, airline);
503 SGGeod lastWptPos = waypoints.back()->getPos();
504 FGGroundNetwork* gn = apt->groundNetwork();
505
506 // Find a route from runway end to parking/gate.
507 if (!gn->exists()) {
508 createDefaultLandingTaxi(ac, apt);
509 return true;
510 }
511
512 FGRunway* rwy = apt->getRunwayByIdent(activeRunway);
513 FGTaxiNodeRef runwayNode;
514 if (gn->getVersion() == 1) {
515 runwayNode = gn->findNearestNodeOnRunwayExit(lastWptPos, rwy);
516 } else {
517 runwayNode = gn->findNearestNode(lastWptPos);
518 }
519 // cerr << "Using network node " << runwayId << endl;
520 // A negative gateId indicates an overflow parking, use a
521 // fallback mechanism for this.
522 // Starting from gate 0 doesn't work, so don't try it
523 FGTaxiRoute taxiRoute;
524 if (runwayNode && gate.isValid()) {
525 taxiRoute = gn->findShortestRoute(runwayNode, gate.parking());
526 }
527
528 if (taxiRoute.empty()) {
529 createDefaultLandingTaxi(ac, apt);
530 return true;
531 }
532
533 FGTaxiNodeRef node;
534 taxiRoute.first();
535 int size = taxiRoute.size();
536 for (int i = 0; i < size; i++) {
537 taxiRoute.next(node, &route);
538 char buffer[32];
539 snprintf(buffer, sizeof(buffer), "landingtaxi-%d-%d", node->getIndex(), i);
540 FGAIWaypoint* wpt =
541 createOnGround(ac, buffer, node->geod(), apt->getElevation(),
542 ac->getPerformance()->vTaxi());
543
544 wpt->setRouteIndex(route);
545 // next WPT must be far enough from last and far enough from parking
546 if ((!waypoints.back() ||
547 SGGeodesy::distanceM(waypoints.back()->getPos(), wpt->getPos()) > 1) &&
548 SGGeodesy::distanceM(gate.parking()->geod(), wpt->getPos()) > 20) {
549 if (waypoints.back()) {
550 int dist = SGGeodesy::distanceM(waypoints.back()->getPos(), wpt->getPos());
551 // pretty near better set speed down
552 if (dist < 10 || (size - i) < 2) {
553 wpt->setSpeed(ac->getPerformance()->vTaxi() / 2);
554 }
555 }
556 pushBackWaypoint(wpt);
557 }
558 }
559 SG_LOG(SG_AI, SG_BULK, "Created taxi from " << runwayNode->getIndex() << " to " << gate.parking()->ident() << " at " << apt->getId());
560
561 time_t now = globals->get_time_params()->get_cur_time();
562
563 arrivalTime = now + calcArrivalTimes();
564 return true;
565}
566
567static double accelDistance(double v0, double v1, double accel)
568{
569 double t = fabs(v1 - v0) / accel; // time in seconds to change velocity
570 // area under the v/t graph: (t * v0) + (dV / 2t) where (dV = v1 - v0)
571 SG_LOG(SG_AI, SG_BULK, "Brakingtime " << t);
572 return t * 0.5 * (v1 + v0);
573}
574
575// find the horizontal distance to gain the specific altitude, holding
576// a constant pitch angle. Used to compute distance based on standard FD/AP
577// PITCH mode prior to VS or CLIMB engaging. Visually, we want to avoid
578// a dip in the nose angle after rotation, during initial climb-out.
579static double pitchDistance(double pitchAngleDeg, double altGainM)
580{
581 return altGainM / tan(pitchAngleDeg * SG_DEGREES_TO_RADIANS);
582}
583
584/*******************************************************************
585 * CreateTakeOff
586 * A note on units:
587 * - Speed -> knots -> nm/hour
588 * - distance along runway =-> meters
589 * - accel / decel -> is given as knots/hour, but this is highly questionable:
590 * for a jet_transport performance class, a accel / decel rate of 5 / 2 is
591 * given respectively. According to performance data.cxx, a value of kts / second seems
592 * more likely however.
593 *
594 ******************************************************************/
596 bool firstFlight,
597 FGAirport* apt,
598 const SGGeod& pos,
599 double speed,
600 const string& fltType)
601{
602 SG_LOG(SG_AI, SG_BULK, "createTakeOff " << apt->getId() << "/" << activeRunway);
603 double accell_point = 105.0;
604 // climb-out angle in degrees. could move this to the perf-db but this
605 // value is pretty sane
606 const double INITIAL_PITCH_ANGLE = 10.0;
607
608 double accel = ac->getPerformance()->acceleration();
609 double vTaxi = ac->getPerformance()->vTaxi();
610 double vRotate = ac->getPerformance()->vRotate();
611 double vTakeoff = ac->getPerformance()->vTakeoff();
612
613 double accelMetric = accel * SG_KT_TO_MPS;
614 double vTaxiMetric = vTaxi * SG_KT_TO_MPS;
615 double vRotateMetric = vRotate * SG_KT_TO_MPS;
616 double vTakeoffMetric = vTakeoff * SG_KT_TO_MPS;
617
618 FGAIWaypoint* wpt;
619 // Get the current active runway, based on code from David Luff
620 // This should actually be unified and extended to include
621 // Preferential runway use schema's
622 // NOTE: DT (2009-01-18: IIRC, this is currently already the case,
623 // because the getActive runway function takes care of that.
624 if (firstFlight) {
625 const string& rwyClass = getRunwayClassFromTrafficType(fltType);
626 double dHeading = ac->getTrafficRef()->getCourse();
627 apt->getDynamics()->getActiveRunway(rwyClass, RunwayAction::TAKEOFF, activeRunway, dHeading);
628 }
629
630 // this is Sentry issue FLIGHTGEAR-DS : happens after reposition,
631 // likely firstFlight is false, but activeRunway is stale
632 if (!apt->hasRunwayWithIdent(activeRunway)) {
633 SG_LOG(SG_AI, SG_WARN, "FGAIFlightPlan::createTakeOff: invalid active runway:" << activeRunway);
634 return false;
635 }
636 SG_LOG(SG_AI, SG_BULK, "Takeoff from airport " << apt->getId() << "/" << activeRunway);
637
638 FGRunway* rwy = apt->getRunwayByIdent(activeRunway);
639 if (!rwy)
640 return false;
641
642 double airportElev = apt->getElevation();
643
644 if (pos.isValid()) {
645 SGGeod runwayEnd = rwy->pointOnCenterlineDisplaced(0);
646 double distM = SGGeodesy::distanceM(pos, runwayEnd);
647 if (distM > accell_point) {
648 SG_LOG(SG_AI, SG_BULK, "Distance down runway " << distM << " " << accell_point);
649 accell_point += distM;
650 }
651 }
652
653 // distance from the runway threshold to accelerate to rotation speed.
654 double d = accelDistance(vTaxiMetric, vRotateMetric, accelMetric) + accell_point;
655 SGGeod rotatePoint = rwy->pointOnCenterlineDisplaced(d);
656 wpt = createOnRunway(ac, "rotate", rotatePoint, airportElev, vRotate);
657 wpt->setFlaps(0.5f);
658 pushBackWaypoint(wpt);
659
660 // After rotation, we still need to accelerate to the take-off speed.
661 double t = d + accelDistance(vRotateMetric, vTakeoffMetric, accelMetric);
662 SGGeod takeoffPoint = rwy->pointOnCenterlineDisplaced(t);
663 wpt = createOnRunway(ac, "takeoff", takeoffPoint, airportElev, vTakeoff);
664 wpt->setGear_down(true);
665 wpt->setFlaps(0.5f);
666 pushBackWaypoint(wpt);
667
668 double vRef = vTakeoff + 20; // climb-out at v2 + 20kts
669
670 // We want gear-up to take place at ~400ft AGL. However, the flightplan
671 // will move onto the next leg once it gets within 2x-speed of the next waypoint.
672 // With closely spaced waypoints on climb-out this can occur almost immediately,
673 // so we put the waypoint further away.
674 double gearUpDist = t + 2 * vRef * SG_FEET_TO_METER + pitchDistance(INITIAL_PITCH_ANGLE, 400 * SG_FEET_TO_METER);
675 SGGeod gearUpPoint = rwy->pointOnCenterlineDisplaced(gearUpDist);
676 wpt = createInAir(ac, "gear-up", gearUpPoint, airportElev + 400, vRef);
677 wpt->setFlaps(0.5f);
678 pushBackWaypoint(wpt);
679
680 // limit climbout speed to 240kts below 10000'
681 double vClimbBelow10000 = std::min(240.0, ac->getPerformance()->vClimb());
682
683 // create two climb-out points. This is important because the first climb point will
684 // be a (sometimes large) turn towards the destination, and we don't want to
685 // commence that turn below 2000'
686 double climbOut = t + 2 * vClimbBelow10000 * SG_FEET_TO_METER + pitchDistance(INITIAL_PITCH_ANGLE, 2000 * SG_FEET_TO_METER);
687 SGGeod climbOutPoint = rwy->pointOnCenterlineDisplaced(climbOut);
688 wpt = createInAir(ac, "2000'", climbOutPoint, airportElev + 2000, vClimbBelow10000);
689 pushBackWaypoint(wpt);
690
691 climbOut = t + 2 * vClimbBelow10000 * SG_FEET_TO_METER + pitchDistance(INITIAL_PITCH_ANGLE, 2500 * SG_FEET_TO_METER);
692 SGGeod climbOutPoint2 = rwy->pointOnCenterlineDisplaced(climbOut);
693 wpt = createInAir(ac, "2500'", climbOutPoint2, airportElev + 2500, vClimbBelow10000);
694 pushBackWaypoint(wpt);
695
696 time_t now = globals->get_time_params()->get_cur_time();
697
698 arrivalTime = now + calcArrivalTimes();
699 return true;
700}
701
702/*******************************************************************
703 * CreateClimb
704 * initialize the Aircraft in a climb.
705 ******************************************************************/
706bool FGAIFlightPlan::createClimb(FGAIAircraft* ac, bool firstFlight,
707 FGAirport* apt, FGAirport* arrival,
708 double speed, double alt,
709 const string& fltType)
710{
711 double vClimb = ac->getPerformance()->vClimb();
712
713 if (firstFlight) {
714 const string& rwyClass = getRunwayClassFromTrafficType(fltType);
715 double dHeading = ac->getTrafficRef()->getCourse();
716 apt->getDynamics()->getActiveRunway(rwyClass, RunwayAction::TAKEOFF, activeRunway, dHeading);
717 }
718
719 if (sid) {
720 for (wpt_vector_iterator i = sid->getFirstWayPoint(); i != sid->getLastWayPoint(); ++i) {
721 pushBackWaypoint(clone(*(i)));
722 }
723 } else {
724 if (!apt->hasRunwayWithIdent(activeRunway))
725 return false;
726
727 FGRunwayRef runway = apt->getRunwayByIdent(activeRunway);
728 SGGeod cur = runway->end();
729 if (!waypoints.empty()) {
730 cur = waypoints.back()->getPos();
731 }
732
733 // compute course towards destination
734 double course = SGGeodesy::courseDeg(cur, arrival->geod());
735 // double distance = SGGeodesy::distanceM(cur, arrival->geod());
736
737 const double headingDiffRunway = SGMiscd::normalizePeriodic(-180, 180, course - runway->headingDeg());
738
739 if (fabs(headingDiffRunway) < 10) {
740 SGGeod climb1 = SGGeodesy::direct(cur, course, 10 * SG_NM_TO_METER);
741 FGAIWaypoint* wpt = createInAir(ac, "10000ft climb", climb1, 10000, vClimb);
742 pushBackWaypoint(wpt);
743
744 SGGeod climb2 = SGGeodesy::direct(cur, course, 20 * SG_NM_TO_METER);
745 wpt = createInAir(ac, "18000ft climb", climb2, 18000, vClimb);
746 pushBackWaypoint(wpt);
747 } else {
748 double initialTurnRadius = getTurnRadius(vClimb, true);
749 SGGeod climb1 = SGGeodesy::direct(cur, runway->headingDeg(), 5 * SG_NM_TO_METER);
750 FGAIWaypoint* wpt = createInAir(ac, "5000ft climb", climb1, 5000, vClimb);
751 pushBackWaypoint(wpt);
752 int rightAngle = headingDiffRunway > 0 ? 90 : -90;
753 int firstTurnIncrement = headingDiffRunway > 0 ? 4 : -4;
754
755 SGGeod firstTurnCenter = SGGeodesy::direct(climb1, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
756 createArc(ac, firstTurnCenter, ac->_getHeading() - rightAngle, course - rightAngle, firstTurnIncrement, initialTurnRadius, 5000, 100, vClimb, "climb-out-%03d");
757 SGGeod climb2 = SGGeodesy::direct(cur, course, 20 * SG_NM_TO_METER);
758 wpt = createInAir(ac, "18000ft climb", climb2, 18000, vClimb);
759 pushBackWaypoint(wpt);
760 }
761 }
762
763 return true;
764}
765
766/*******************************************************************
767 * CreateDescent
768 * Generate a flight path from the last waypoint of the cruise to
769 * the permission to land point
770 ******************************************************************/
771bool FGAIFlightPlan::createDescent(FGAIAircraft* ac,
772 FGAirport* apt,
773 const SGGeod& current,
774 double speed,
775 double alt,
776 const string& fltType,
777 double requiredDistance)
778{
779 // The descent path contains the following phases:
780 // 1) a semi circle towards runway end
781 // 2) a linear glide path from the initial position to
782 // 3) a semi circle turn to final
783 // 4) approach
784 double vDescent = ac->getPerformance()->vDescent();
785 // double vApproach = ac->getPerformance()->vApproach();
786
787 //Beginning of Descent
788 const string& rwyClass = getRunwayClassFromTrafficType(fltType);
789 double heading = ac->getTrueHeadingDeg();
790 apt->getDynamics()->getActiveRunway(rwyClass, RunwayAction::LANDING, activeRunway, heading);
791 if (!apt->hasRunwayWithIdent(activeRunway)) {
792 SG_LOG(SG_AI, SG_WARN, ac->getCallSign() << "| FGAIFlightPlan::createDescent: No such runway " << activeRunway << " at " << apt->ident());
793 return false;
794 }
795
796 FGRunwayRef rwy = apt->getRunwayByIdent(activeRunway);
797 if (waypoints.size() == 0 && ac->getTrueHeadingDeg() == 0) {
798 // we obviously have no previous state and are free to position the aircraft
799 double courseTowardsThreshold = SGGeodesy::courseDeg(current, rwy->pointOnCenterlineDisplaced(0));
800 ac->setHeading(courseTowardsThreshold);
801 }
802
803 SGGeod threshold = rwy->threshold();
804 double currElev = threshold.getElevationFt();
805 double altDiff = alt - currElev - 2000;
806
807 // depending on entry we differ approach (teardrop/direct/parallel)
808
809 double initialTurnRadius = getTurnRadius(vDescent, true);
810 //double finalTurnRadius = getTurnRadius(vApproach, true);
811
812 // get length of the downwind leg for the intended runway
813 double distanceOut = apt->getDynamics()->getRunwayQueue(rwy->name())->getApproachDistance(); //12 * SG_NM_TO_METER;
814
815 // tells us the direction we have to turn
816 const double headingDiffRunway = SGMiscd::normalizePeriodic(-180, 180, ac->getTrueHeadingDeg() - rwy->headingDeg());
817 double lateralOffset = initialTurnRadius;
818 if (headingDiffRunway > 0.0) {
819 lateralOffset *= -1.0;
820 }
821
822 SGGeod initialTarget = rwy->pointOnCenterline(-distanceOut);
823// SGGeod otherRwyEnd = rwy->pointOnCenterline(rwy->lengthM());
824 SGGeod secondaryTarget =
825 rwy->pointOffCenterline(-2 * distanceOut, lateralOffset);
826 SGGeod secondHoldCenter =
827 rwy->pointOffCenterline(-3 * distanceOut, lateralOffset);
828// SGGeod refPoint = rwy->pointOnCenterline(0);
829 double distance = SGGeodesy::distanceM(current, initialTarget);
830 double azimuth = SGGeodesy::courseDeg(current, initialTarget);
831 double secondaryAzimuth = SGGeodesy::courseDeg(current, secondaryTarget);
832 double initialHeadingDiff = SGMiscd::normalizePeriodic(-180, 180, azimuth - secondaryAzimuth);
833 double courseTowardsThreshold = SGGeodesy::courseDeg(current, rwy->pointOnCenterlineDisplaced(0));
834 // double courseTowardsRwyEnd = SGGeodesy::courseDeg(current, otherRwyEnd);
835 double headingDiffToRunwayThreshold = SGMiscd::normalizePeriodic(-180, 180, ac->getTrueHeadingDeg() - courseTowardsThreshold);
836 // double headingDiffToRunwayEnd = SGMiscd::normalizePeriodic(-180, 180, ac->getTrueHeadingDeg() - courseTowardsRwyEnd);
837
838 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| "
839 << " WPs : " << waypoints.size() << " Heading Diff (rwy) : " << headingDiffRunway << " Distance : " << distance << " Azimuth : " << azimuth << " Heading : " << ac->getTrueHeadingDeg() << " Initial Headingdiff " << initialHeadingDiff << " Lateral : " << lateralOffset);
840 // Erase the two bogus BOD points: Note check for conflicts with scripted AI flightPlans
841 IncrementWaypoint(false);
842 IncrementWaypoint(false);
843
844 if (fabs(headingDiffRunway) >= 30 && fabs(headingDiffRunway) <= 150) {
845 if (distance < (2 * initialTurnRadius)) {
846 // Too near so we pass over and enter over other side
847 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| Enter near S curve");
848 secondaryTarget =
849 rwy->pointOffCenterline(-2 * distanceOut, -lateralOffset);
850 secondHoldCenter =
851 rwy->pointOffCenterline(-3 * distanceOut, -lateralOffset);
852
853 // Entering not "straight" into runway so we do a s-curve
854 int rightAngle = headingDiffRunway > 0 ? 90 : -90;
855 int firstTurnIncrement = headingDiffRunway > 0 ? 2 : -2;
856
857 SGGeod firstTurnCenter = SGGeodesy::direct(current, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
858 SGGeod newCurrent = current;
859 // If we are not far enough cross over
860 if (abs(headingDiffToRunwayThreshold) < 90) {
861 newCurrent = SGGeodesy::direct(current, ac->getTrueHeadingDeg(), distance + 1000);
862 firstTurnCenter = SGGeodesy::direct(newCurrent, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
863 createLine(ac, current, ac->getTrueHeadingDeg(), distance + 1000, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, 0, vDescent, "move%03d");
864 }
865 int offset = 1000;
866 while (SGGeodesy::distanceM(firstTurnCenter, secondaryTarget) < 2 * initialTurnRadius) {
867 newCurrent = SGGeodesy::direct(newCurrent, ac->getTrueHeadingDeg(), offset += 100);
868 firstTurnCenter = SGGeodesy::direct(newCurrent, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
869 }
870 const double dHeading = VectorMath::outerTangentsAngle(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius)[0];
871 createArc(ac, firstTurnCenter, ac->_getHeading() - rightAngle, dHeading - rightAngle, firstTurnIncrement, initialTurnRadius, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "near-initialturn%03d");
872 double length = VectorMath::innerTangentsLength(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius);
873 createLine(ac, waypoints.back()->getPos(), dHeading, length, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 2, vDescent, "descent%03d");
874 int startVal = SGMiscd::normalizePeriodic(0, 360, dHeading - rightAngle);
875 int endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg() - rightAngle);
876 // Turn into runway
877 createArc(ac, secondaryTarget, startVal, endVal, firstTurnIncrement, initialTurnRadius,
878 waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "turn%03d");
879 } else {
880 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| Enter far S curve");
881 // Entering not "straight" into runway so we do a s-curve
882 int rightAngle = headingDiffRunway > 0 ? 90 : -90;
883 int firstTurnIncrement = headingDiffRunway > 0 ? 2 : -2;
884 SGGeod firstTurnCenter = SGGeodesy::direct(current, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
885 int innerTangent = headingDiffRunway < 0 ? 0 : 1;
886 int offset = 1000;
887 while (SGGeodesy::distanceM(firstTurnCenter, secondaryTarget) < 2 * initialTurnRadius) {
888 secondaryTarget = rwy->pointOffCenterline(-2 * distanceOut + (offset += 1000), lateralOffset);
889 }
890 const double dHeading = VectorMath::innerTangentsAngle(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius)[innerTangent];
891 createArc(ac, firstTurnCenter, ac->_getHeading() - rightAngle, dHeading - rightAngle, firstTurnIncrement, initialTurnRadius, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 8, vDescent, "far-initialturn%03d");
892 double length = VectorMath::innerTangentsLength(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius);
893 createLine(ac, waypoints.back()->getPos(), dHeading, length, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff * 0.75, vDescent, "descent%03d");
894 int startVal = SGMiscd::normalizePeriodic(0, 360, dHeading + rightAngle);
895 int endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg() + rightAngle);
896 // Turn into runway
897 createArc(ac, secondaryTarget, startVal, endVal, firstTurnIncrement * -1, initialTurnRadius,
898 waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 8, vDescent, "s-turn%03d");
899 }
900 } else if (fabs(headingDiffRunway) >= 150) {
901 // We are entering downwind
902 if (distance < (2 * initialTurnRadius)) {
903 // Too near so we pass over and enter over other side
904 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| Enter near downrunway");
905 secondaryTarget =
906 rwy->pointOffCenterline(-2 * distanceOut, -lateralOffset);
907 secondHoldCenter =
908 rwy->pointOffCenterline(-3 * distanceOut, -lateralOffset);
909
910 // Entering not "straight" into runway so we do a s-curve
911 int rightAngle = azimuth > 0 ? 90 : -90;
912 int firstTurnIncrement = azimuth > 0 ? 2 : -2;
913
914 SGGeod firstTurnCenter = SGGeodesy::direct(current, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
915 // rwy->headingDeg()-rightAngle
916 int endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg() - 180);
917 SGGeod secondTurnCenter = SGGeodesy::direct(firstTurnCenter, endVal, 2 * initialTurnRadius);
918 createArc(ac, firstTurnCenter, ac->_getHeading() - rightAngle, endVal, firstTurnIncrement, initialTurnRadius, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "d-near-initialturn%03d");
919 endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg());
920 // int endVal2 = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg()-rightAngle);
921 const double endVal2 = VectorMath::outerTangentsAngle(secondTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius)[0];
922 createArc(ac, secondTurnCenter, endVal, endVal2 + rightAngle, -firstTurnIncrement, initialTurnRadius, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "secondturn%03d");
923 //outer
924 double length = VectorMath::outerTangentsLength(secondTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius);
925 createLine(ac, waypoints.back()->getPos(), endVal2, length, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "descent%03d");
926 endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg() + rightAngle);
927 // Turn into runway
928 createArc(ac, secondaryTarget, endVal2 + rightAngle, endVal, -firstTurnIncrement, initialTurnRadius,
929 waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 4, vDescent, "turn%03d");
930 } else {
931 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| Enter far S downrunway");
932 // Entering not "straight" into runway so we do a s-curve
933 int rightAngle = headingDiffRunway > 0 ? 90 : -90;
934 int firstTurnIncrement = headingDiffRunway > 0 ? 2 : -2;
935 int innerTangent = headingDiffRunway < 0 ? 0 : 1;
936 SGGeod firstTurnCenter = SGGeodesy::direct(current, ac->getTrueHeadingDeg() + rightAngle, initialTurnRadius);
937 const double dHeading = VectorMath::innerTangentsAngle(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius)[innerTangent];
938 createArc(ac, firstTurnCenter, ac->_getHeading() - rightAngle, dHeading - rightAngle, firstTurnIncrement, initialTurnRadius, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 3, vDescent, "d-far-initialturn%03d");
939 double length = VectorMath::innerTangentsLength(firstTurnCenter, secondaryTarget, initialTurnRadius, initialTurnRadius);
940 createLine(ac, waypoints.back()->getPos(), dHeading, length, waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 3, vDescent, "descent%03d");
941 int startVal = SGMiscd::normalizePeriodic(0, 360, dHeading + rightAngle);
942 int endVal = SGMiscd::normalizePeriodic(0, 360, rwy->headingDeg() + rightAngle);
943 // Turn into runway
944 createArc(ac, secondaryTarget, startVal, endVal, -firstTurnIncrement, initialTurnRadius,
945 waypoints.size() > 0 ? waypoints.back()->getAltitude() : alt, altDiff / 3, vDescent, "d-s-turn%03d");
946 }
947 } else {
948 SG_LOG(SG_AI, SG_BULK, ac->getCallSign() << "| Enter far straight");
949 // Entering "straight" into runway so only one turn
950 int rightAngle = headingDiffRunway > 0 ? 90 : -90;
951 SGGeod firstTurnCenter = SGGeodesy::direct(current, ac->getTrueHeadingDeg() - rightAngle, initialTurnRadius);
952 int firstTurnIncrement = headingDiffRunway > 0 ? -2 : 2;
953 const double dHeading = rwy->headingDeg();
954 createArc(ac, firstTurnCenter, ac->_getHeading() + rightAngle, dHeading + rightAngle, firstTurnIncrement, initialTurnRadius, ac->getAltitude(), altDiff / 3, vDescent, "straight_turn_%03d");
955 }
956
957 time_t now = globals->get_time_params()->get_cur_time();
958
959 arrivalTime = now + calcArrivalTimes();
960
961 //choose a distance to the runway such that it will take at least 60 seconds more
962 // time to get there than the previous aircraft.
963 // Don't bother when aircraft need to be repositioned, because that marks the initialization phased...
964 return true;
965}
966
967/*******************************************************************
968 * CreateHold
969 * Generate a hold flight path from the permission to land point
970 * Exactly one hold pattern
971 ******************************************************************/
972bool FGAIFlightPlan::createHold(FGAIAircraft* ac,
973 FGAirport* apt,
974 const SGGeod& current,
975 double speed,
976 double alt,
977 const string& fltType,
978 double requiredDistance)
979{
980 // Beginning of Descent
981 const string& rwyClass = getRunwayClassFromTrafficType(fltType);
982 double vDescent = ac->getPerformance()->vDescent();
983 // double vApproach = ac->getPerformance()->vApproach();
984 double initialTurnRadius = getTurnRadius(vDescent, true);
985 double dHeading = ac->getTrueHeadingDeg();
986 apt->getDynamics()->getActiveRunway(rwyClass, RunwayAction::LANDING, activeRunway,
987 dHeading);
988 if (!apt->hasRunwayWithIdent(activeRunway)) {
989 SG_LOG(SG_AI, SG_WARN, ac->getCallSign() << "| FGAIFlightPlan::createHold: No such runway " << activeRunway << " at " << apt->ident());
990 return false;
991 }
992 FGRunwayRef rwy = apt->getRunwayByIdent(activeRunway);
993 double currentAltitude = waypoints.back()->getAltitude();
994 double distanceOut = apt->getDynamics()->getRunwayQueue(rwy->name())->getApproachDistance(); //12 * SG_NM_TO_METER;
995 double lateralOffset = initialTurnRadius;
996
997 SGGeod secondaryTarget = rwy->pointOffCenterline(-2 * distanceOut, lateralOffset);
998 SGGeod secondHoldCenter = rwy->pointOffCenterline(-4 * distanceOut, lateralOffset);
999
1000 createArc(ac, secondaryTarget, rwy->headingDeg() - 90, rwy->headingDeg() + 90, 5, initialTurnRadius,
1001 currentAltitude, 0, vDescent, "hold_1_%03d");
1002 createArc(ac, secondHoldCenter, rwy->headingDeg() + 90, rwy->headingDeg() - 90, 5, initialTurnRadius,
1003 currentAltitude, 0, vDescent, "hold_2_%03d");
1004
1005 time_t now = globals->get_time_params()->get_cur_time();
1006 arrivalTime = now + calcArrivalTimes();
1007
1008 return true;
1009}
1015{
1016 FGNavRecord* gs = rwy->glideslope();
1017 if (!gs) {
1018 return -1;
1019 }
1020
1021 SGVec3d runwayPosCart = SGVec3d::fromGeod(rwy->pointOnCenterline(0.0));
1022 // compute a unit vector in ECF cartesian space, from the runway beginning to the end
1023 SGVec3d runwayDirectionVec = normalize(SGVec3d::fromGeod(rwy->end()) - runwayPosCart);
1024 SGVec3d gsTransmitterVec = gs->cart() - runwayPosCart;
1025
1026 // project the gsTransmitterVec along the runwayDirctionVec to get out
1027 // final value (in metres)
1028 double dist = dot(runwayDirectionVec, gsTransmitterVec);
1029 return dist;
1030}
1031
1032/*******************************************************************
1033 * CreateLanding (Leg 8)
1034 * Create a flight path from the "permission to land" point (currently
1035 hardcoded at 5000 meters from the threshold) to the threshold, at
1036 a standard glide slope angle of 3 degrees.
1037 Position : 50.0354 8.52592 384 364 11112
1038 ******************************************************************/
1039bool FGAIFlightPlan::createLanding(FGAIAircraft* ac, FGAirport* apt,
1040 const string& fltType)
1041{
1042 double vTouchdown = ac->getPerformance()->vTouchdown();
1043 double vTaxi = ac->getPerformance()->vTaxi();
1044 double decel = ac->getPerformance()->decelerationOnGround();
1045 double vApproach = ac->getPerformance()->vApproach();
1046
1047 double vTouchdownMetric = vTouchdown * SG_KT_TO_MPS;
1048 double vTaxiMetric = vTaxi * SG_KT_TO_MPS;
1049 double decelMetric = decel * SG_KT_TO_MPS;
1050
1051 char buffer[30];
1052 if (!apt->hasRunwayWithIdent(activeRunway)) {
1053 SG_LOG(SG_AI, SG_WARN, "FGAIFlightPlan::createLanding: No such runway " << activeRunway << " at " << apt->ident());
1054 return false;
1055 }
1056
1057
1058 FGRunway* rwy = apt->getRunwayByIdent(activeRunway);
1059 if (!rwy) {
1060 return false;
1061 }
1062
1063 // depending on entry we differ approach (teardrop/direct/parallel)
1064 const double headingDiff = ac->getBearing(rwy->headingDeg());
1065 SG_LOG(SG_AI, SG_BULK, "Heading Diff " << headingDiff);
1066
1067 SGGeod threshold = rwy->threshold();
1068 double currElev = threshold.getElevationFt();
1069
1070 double touchdownDistance = runwayGlideslopeTouchdownDistance(rwy);
1071 if (touchdownDistance < 0.0) {
1072 double landingLength = rwy->lengthM() - (rwy->displacedThresholdM());
1073 // touchdown 25% of the way along the landing area
1074 touchdownDistance = rwy->displacedThresholdM() + (landingLength * 0.25);
1075 }
1076
1077 const double tanGlideslope = tan(3.0);
1078
1079 SGGeod coord;
1080 // find glideslope entry point, 2000' above touchdown elevation
1081 double glideslopeEntry = -((2000 * SG_FEET_TO_METER) / tanGlideslope) + touchdownDistance;
1082
1083 snprintf(buffer, sizeof(buffer), "Glideslope begin Rwy %s", activeRunway.c_str());
1084
1085 FGAIWaypoint* wpt = createInAir(ac, buffer, rwy->pointOnCenterline(-glideslopeEntry),
1086 currElev + 2000, vApproach);
1087 wpt->setGear_down(true);
1088 wpt->setFlaps(1.0f);
1089 wpt->setSpeedBrakes(1.0f);
1090 pushBackWaypoint(wpt);
1091
1092 // deceleration point, 500' above touchdown elevation - slow from approach speed
1093 // to touchdown speed
1094 double decelPoint = -((500 * SG_FEET_TO_METER) / tanGlideslope) + touchdownDistance;
1095 wpt = createInAir(ac, "500 ft decel", rwy->pointOnCenterline(-decelPoint),
1096 currElev + 500, vTouchdown);
1097 wpt->setGear_down(true);
1098 wpt->setFlaps(1.0f);
1099 wpt->setSpeedBrakes(1.0f);
1100 pushBackWaypoint(wpt);
1101
1102 // compute elevation above the runway start, based on a 3-degree glideslope
1103 double heightAboveRunwayStart = touchdownDistance *
1104 tan(3.0 * SG_DEGREES_TO_RADIANS) * SG_METER_TO_FEET;
1105 wpt = createInAir(ac, "CrossThreshold", rwy->begin(),
1106 heightAboveRunwayStart + currElev, vTouchdown);
1107 wpt->setGear_down(true);
1108 wpt->setFlaps(1.0f);
1109 wpt->setSpeedBrakes(1.0f);
1110 pushBackWaypoint(wpt);
1111
1112 double rolloutDistance = accelDistance(vTouchdownMetric, vTaxiMetric, decelMetric);
1113
1114 SG_LOG(SG_AI, SG_BULK, "Landing " << glideslopeEntry << "\t" << decelPoint << " Rollout " << rolloutDistance);
1115
1116 int nPoints = (int)(rolloutDistance / 60);
1117 for (int i = 1; i <= nPoints; i++) {
1118 snprintf(buffer, sizeof(buffer), "rollout%03d", i);
1119 double t = 1 - pow((double)(nPoints - i), 2) / pow(nPoints, 2);
1120 coord = rwy->pointOnCenterline(touchdownDistance + (rolloutDistance * t));
1121 double vel = (vTouchdownMetric * (1.0 - t)) + (vTaxiMetric * t);
1122 wpt = createOnRunway(ac, buffer, coord, currElev, vel);
1123 wpt->setFlaps(1.0f);
1124 wpt->setSpeedBrakes(1.0f);
1125 wpt->setSpoilers(1.0f);
1126 wpt->setCrossat(currElev);
1127 pushBackWaypoint(wpt);
1128 }
1129
1130 wpt->setSpeed(vTaxi);
1131 double mindist = (1.1 * rolloutDistance) + touchdownDistance;
1132
1133 FGGroundNetwork* gn = apt->groundNetwork();
1134 if (!gn) {
1135 SG_LOG(SG_AI, SG_DEBUG, "No groundnet " << apt->getId() << " no landing created.");
1136 return true;
1137 }
1138
1139 // We project down the runway and then search for an exit
1140 coord = rwy->pointOnCenterline(mindist);
1141 FGTaxiNodeRef tn;
1142 if (gn->getVersion() > 0) {
1143 tn = gn->findNearestNodeOnRunwayExit(coord, rwy);
1144 } else {
1145 tn = gn->findNearestNode(coord);
1146 }
1147
1148 if (tn) {
1149 wpt = createOnRunway(ac, "runwayexit", tn->geod(), currElev, vTaxi);
1150 wpt->setFlaps(1.0f);
1151 wpt->setSpeedBrakes(1.0f);
1152 wpt->setSpoilers(0.0f);
1153 pushBackWaypoint(wpt);
1154 }
1155
1156 time_t now = globals->get_time_params()->get_cur_time();
1157 arrivalTime = now + calcArrivalTimes();
1158
1159 return true;
1160}
1161
1162/*******************************************************************
1163 * createParking Leg 9
1164 * initialize the Aircraft at the parking location
1165 ******************************************************************/
1166bool FGAIFlightPlan::createParking(FGAIAircraft* ac, FGAirport* apt,
1167 double radius)
1168{
1169 FGAIWaypoint* wpt;
1170 double aptElev = apt->getElevation();
1171 double vTaxi = ac->getPerformance()->vTaxi();
1172 double vTaxiReduced = vTaxi * (2.0 / 3.0);
1173 if (!gate.isValid()) {
1174 wpt = createOnGround(ac, "END-ParkingInvalidGate", apt->geod(), aptElev,
1175 vTaxiReduced);
1176 pushBackWaypoint(wpt);
1177 return true;
1178 }
1179
1180 FGParking* parking = gate.parking();
1181 double reverseHeading = SGMiscd::normalizePeriodic(0, 360, parking->getHeading() + 180.0);
1182 double az; // unused
1183 SGGeod pos;
1184
1185 SGGeodesy::direct(parking->geod(), reverseHeading, 18,
1186 pos, az);
1187
1188 wpt = createOnGround(ac, "parking1", pos, aptElev, 3);
1189 pushBackWaypoint(wpt);
1190
1191 SGGeodesy::direct(parking->geod(), reverseHeading, 14,
1192 pos, az);
1193 wpt = createOnGround(ac, "parking2", pos, aptElev, 3);
1194 pushBackWaypoint(wpt);
1195
1196 SGGeodesy::direct(parking->geod(), reverseHeading, 10,
1197 pos, az);
1198 wpt = createOnGround(ac, "parking3", pos, aptElev, 2);
1199 pushBackWaypoint(wpt);
1200
1201 SGGeodesy::direct(parking->geod(), reverseHeading, 6,
1202 pos, az);
1203 wpt = createOnGround(ac, "parking4", pos, aptElev, 2);
1204 pushBackWaypoint(wpt);
1205
1206 SGGeodesy::direct(parking->geod(), reverseHeading, 3,
1207 pos, az);
1208 wpt = createOnGround(ac, "parking5", pos, aptElev, 2);
1209 pushBackWaypoint(wpt);
1210
1211 char buffer[30];
1212 snprintf(buffer, sizeof(buffer), "Parking-%s", parking->getName().c_str());
1213
1214 wpt = createOnGround(ac, buffer, parking->geod(), aptElev, vTaxiReduced / 3);
1215 pushBackWaypoint(wpt);
1216 SGGeodesy::direct(parking->geod(), parking->getHeading(), 2,
1217 pos, az);
1218 wpt = createOnGround(ac, "Beyond-Parking", pos, aptElev, vTaxiReduced / 3);
1219 pushBackWaypoint(wpt);
1220 SGGeodesy::direct(parking->geod(), parking->getHeading(), 3,
1221 pos, az);
1222 wpt = createOnGround(ac, "END-Parking", pos, aptElev, vTaxiReduced / 3);
1223 pushBackWaypoint(wpt);
1224 return true;
1225}
1226
1250const char* FGAIFlightPlan::getRunwayClassFromTrafficType(const string& fltType)
1251{
1252 if ((fltType == "gate") || (fltType == "cargo")) {
1253 return "com";
1254 }
1255 if (fltType == "ga") {
1256 return "gen";
1257 }
1258 if (fltType == "ul") {
1259 return "ul";
1260 }
1261 if ((fltType == "mil-fighter") || (fltType == "mil-transport")) {
1262 return "mil";
1263 }
1264 return "com";
1265}
1266
1267
1268double FGAIFlightPlan::getTurnRadius(double speed, bool inAir)
1269{
1270 double turn_radius;
1271 if (!inAir) {
1272 turn_radius = ((360 / 30) * fabs(speed)) / (2 * M_PI);
1273 } else {
1274 turn_radius = 0.1911 * speed * speed; // an estimate for 25 degrees bank
1275 }
1276 return turn_radius;
1277}
double latitude
Definition ADA.cxx:53
double longitude
Definition ADA.cxx:54
static double accelDistance(double v0, double v1, double accel)
static double pitchDistance(double pitchAngleDeg, double altGainM)
static double runwayGlideslopeTouchdownDistance(FGRunway *rwy)
compute the distance along the centerline, to the ILS glideslope transmitter.
#define M_PI
Definition FGJSBBase.h:50
#define i(x)
SGSharedPtr< FGTaxiNode > FGTaxiNodeRef
SGSharedPtr< FGRunway > FGRunwayRef
double getBearing(double crse)
Returns a normalised bearing.
PerformanceData * getPerformance()
double getAltitude() const
FGAISchedule * getTrafficRef()
double getTrueHeadingDeg() const
Definition AIBase.hxx:152
const std::string & getCallSign() const
Definition AIBase.hxx:367
double _getHeading() const
Definition AIBase.cxx:1112
void setHeading(double heading)
Definition AIBase.hxx:414
void IncrementWaypoint(bool erase)
const char * getRunwayClassFromTrafficType(const std::string &fltType)
bool createTakeOff(FGAIAircraft *, bool, FGAirport *, const SGGeod &pos, double speed, const std::string &flightType)
bool create(FGAIAircraft *, FGAirport *dep, FGAirport *arr, int leg, double alt, double speed, double lat, double lon, bool firstLeg, double radius, const std::string &fltType, const std::string &aircraftType, const std::string &airline, double distance)
int getLeg() const
bool createPushBack(FGAIAircraft *, bool, FGAirport *, double radius, const std::string &, const std::string &, const std::string &)
double getCourse()
Definition Schedule.hxx:124
void setApproachLights()
void setGear_down(bool grd)
void setAltitude(double alt)
double getSpeed()
void setCrossat(double val)
void setTakeOffLights()
void setName(const std::string &nam)
double getLatitude() const
void setStrobeLight(bool strobe)
void setOn_ground(bool grn)
double getCrossat()
void setTrackLength(double tl)
const std::string & getName()
void setSpoilers(double val)
double getFlaps()
void setFinished(bool fin)
void setLongitude(double lon)
void setLatitude(double lat)
void setSpeed(double spd)
void setCruiseLights()
bool getLandingLight()
bool getStrobeLight()
void setLandingLight(bool ldg)
void setPowerDownLights()
double getLongitude() const
void setFlaps(double val)
void setTaxiLight(bool taxi)
void setGroundLights()
double getAltitude() const
void setSpeedBrakes(double val)
const SGGeod & getPos()
void setNavLight(bool nav)
void setRouteIndex(int rte)
FGAirportDynamicsRef getDynamics() const
Definition airport.cxx:1048
double getElevation() const
Definition airport.hxx:61
FGRunwayRef getRunwayByIdent(const std::string &aIdent) const
Definition airport.cxx:182
bool hasRunwayWithIdent(const std::string &aIdent) const
Definition airport.cxx:162
const std::string & getId() const
Definition airport.hxx:53
FGGroundNetwork * groundNetwork() const
Definition airport.cxx:1053
SGTime * get_time_params() const
Definition globals.hxx:311
FGTaxiNodeRef findNearestNodeOnRunwayExit(const SGGeod &aGeod, FGRunway *aRunway=NULL) const
Returns the nearest node in that is in direction of runway heading.
FGTaxiNodeRef findNearestNodeOnRunwayEntry(const SGGeod &aGeod) const
FGTaxiNodeRef findNearestNode(const SGGeod &aGeod) const
FGTaxiRoute findShortestRoute(FGTaxiNode *start, FGTaxiNode *end, bool fullSearch=true)
double getHeading() const
Definition parking.hxx:57
std::string getName() const
Definition parking.hxx:62
FGTaxiNodeRef getPushBackPoint()
Definition parking.hxx:65
virtual const SGGeod & geod() const
virtual const SGVec3d & cart() const
The cartesian position associated with this object.
const std::string & ident() const
double headingDeg() const
Runway heading in degrees.
SGGeod pointOnCenterline(double aOffset) const
Retrieve a position on the extended centerline.
double lengthM() const
FGNavRecord * glideslope() const
retrieve the associated glideslope transmitter, if one is defined.
Definition runways.cxx:129
SGGeod pointOnCenterlineDisplaced(double aOffset) const
Retrieve a position on the extended centerline.
Definition runways.cxx:104
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
Definition runways.cxx:94
SGGeod threshold() const
Get the (possibly displaced) threshold point.
Definition runways.cxx:99
SGGeod begin() const
Get the runway beginning point - this is syntatic sugar, equivalent to calling pointOnCenterline(0....
Definition runways.cxx:89
double displacedThresholdM() const
Definition runways.hxx:69
bool next(FGTaxiNodeRef &nde, int *rte)
double acceleration() const
double vClimb() const
double decelerationOnGround() const
double vDescent() const
double vTakeoff() const
double vTaxi() const
double vTouchdown() const
double vRotate() const
double vApproach() const
static double outerTangentsLength(SGGeod m1, SGGeod m2, double r1, double r2)
Length of outer tangent between two circles.
static std::array< double, 2 > innerTangentsAngle(SGGeod m1, SGGeod m2, double r1, double r2)
Angles of inner tangent between two circles.
static double innerTangentsLength(SGGeod m1, SGGeod m2, double r1, double r2)
Length of inner tangent between two circles.
static std::array< double, 2 > outerTangentsAngle(SGGeod m1, SGGeod m2, double r1, double r2)
Angles of outer tangent between two circles normalized to 0-360.
FGGlobals * globals
Definition globals.cxx:142
@ HOLD_PATTERN
@ RUNWAY_TAXI
@ PARKING_TAXI
@ STARTUP_PUSHBACK
float abs(float f)
Definition Airplane.cpp:21