]> git.mxchange.org Git - flightgear.git/blob - src/Traffic/Schedule.cxx
Make traffic take-off roll look a little better.
[flightgear.git] / src / Traffic / Schedule.cxx
1 /******************************************************************************
2  * Schedule.cxx
3  * Written by Durk Talsma, started May 5, 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  *
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #define BOGUS 0xFFFF
29
30 #include <stdlib.h>
31 #include <time.h>
32 #include <iostream>
33 #include <fstream>
34
35
36 #include <string>
37 #include <vector>
38 #include <algorithm>
39
40 #include <simgear/compiler.h>
41 #include <simgear/sg_inlines.h>
42 #include <simgear/math/sg_geodesy.hxx>
43 #include <simgear/props/props.hxx>
44 #include <simgear/structure/subsystem_mgr.hxx>
45 #include <simgear/xml/easyxml.hxx>
46
47 #include <AIModel/AIFlightPlan.hxx>
48 #include <AIModel/AIManager.hxx>
49 #include <AIModel/AIAircraft.hxx>
50 #include <Airports/simple.hxx>
51 #include <Main/fg_init.hxx>   // That's pretty ugly, but I need fgFindAirportID
52
53
54 #include "SchedFlight.hxx"
55 #include "TrafficMgr.hxx"
56
57 /******************************************************************************
58  * the FGAISchedule class contains data members and code to maintain a
59  * schedule of Flights for an artificially controlled aircraft.
60  *****************************************************************************/
61 FGAISchedule::FGAISchedule()
62 {
63   firstRun     = true;
64   AIManagerRef = 0;
65
66   heavy = false;
67   radius = 0;
68   groundOffset = 0;
69   distanceToUser = 0;
70   valid = true;
71   lastRun = 0;
72   //score = 0;
73 }
74
75 /*
76 FGAISchedule::FGAISchedule(string    mdl, 
77                            string    liv, 
78                            string    reg, 
79                            bool      hvy, 
80                            string act, 
81                            string arln, 
82                            string mclass, 
83                            string fltpe,
84                            double rad,
85                            double grnd,
86                            int    scre,
87                            FGScheduledFlightVec flt)*/
88 FGAISchedule::FGAISchedule(string model, 
89                            string lvry,
90                            string port, 
91                            string reg, 
92                            string flightId,
93                            bool   hvy, 
94                            string act, 
95                            string arln, 
96                            string mclass, 
97                            string fltpe, 
98                            double rad, 
99                            double grnd)
100 {
101   modelPath        = model; 
102   livery           = lvry; 
103   homePort         = port;
104   registration     = reg;
105   flightIdentifier = flightId;
106   acType           = act;
107   airline          = arln;
108   m_class          = mclass;
109   flightType       = fltpe;
110   radius           = rad;
111   groundOffset     = grnd;
112   distanceToUser   = 0;
113   heavy            = hvy;
114   /*for (FGScheduledFlightVecIterator i = flt.begin();
115        i != flt.end();
116        i++)
117     flights.push_back(new FGScheduledFlight((*(*i))));*/
118   AIManagerRef     = 0;
119   score    =         0;
120   firstRun         = true;
121   runCount         = 0;
122   hits             = 0;
123   lastRun          = 0;
124   initialized      = false;
125   valid            = true;
126 }
127
128 FGAISchedule::FGAISchedule(const FGAISchedule &other)
129 {
130   modelPath          = other.modelPath;
131   homePort           = other.homePort;
132   livery             = other.livery;
133   registration       = other.registration;
134   heavy              = other.heavy;
135   flightIdentifier   = other.flightIdentifier;
136   flights            = other.flights;
137   AIManagerRef       = other.AIManagerRef;
138   acType             = other.acType;
139   airline            = other.airline;
140   m_class            = other.m_class;
141   firstRun           = other.firstRun;
142   radius             = other.radius;
143   groundOffset       = other.groundOffset;
144   flightType         = other.flightType;
145   score              = other.score;
146   distanceToUser     = other.distanceToUser;
147   currentDestination = other.currentDestination;
148   firstRun           = other.firstRun;
149   runCount           = other.runCount;
150   hits               = other.hits;
151   lastRun            = other.lastRun;
152   initialized        = other.initialized;
153   valid              = other.valid;
154 }
155
156
157
158 FGAISchedule::~FGAISchedule()
159 {
160     // remove related object from AI manager
161     if (AIManagerRef)
162     {
163         FGAIManager* aimgr = (FGAIManager *) globals-> get_subsystem("ai-model");
164         if (aimgr)
165             aimgr->destroyObject(AIManagerRef);
166         AIManagerRef = 0;
167     }
168
169 /*  for (FGScheduledFlightVecIterator flt = flights.begin(); flt != flights.end(); flt++)
170     {
171       delete (*flt);
172     }
173   flights.clear();*/
174
175
176 bool FGAISchedule::init()
177 {
178   //tm targetTimeDate;
179   //SGTime* currTimeDate = globals->get_time_params();
180
181   //tm *temp = currTimeDate->getGmt();
182   //char buffer[512];
183   //sgTimeFormatTime(&targetTimeDate, buffer);
184   //cout << "Scheduled Time " << buffer << endl; 
185   //cout << "Time :" << time(NULL) << " SGTime : " << sgTimeGetGMT(temp) << endl;
186   /*for (FGScheduledFlightVecIterator i = flights.begin(); 
187        i != flights.end(); 
188        i++)
189     {
190       //i->adjustTime(now);
191       if (!((*i)->initializeAirports()))
192         return false;
193     } */
194   //sort(flights.begin(), flights.end());
195   // Since time isn't initialized yet when this function is called,
196   // Find the closest possible airport.
197   // This should give a reasonable initialization order. 
198   //setClosestDistanceToUser();
199   return true;
200 }
201
202 bool FGAISchedule::update(time_t now, const SGVec3d& userCart)
203
204   time_t 
205     totalTimeEnroute, 
206     elapsedTimeEnroute,
207     //remainingTimeEnroute,
208     deptime = 0;
209   if (!valid) {
210     return false;
211   }
212   scheduleFlights(now);
213   if (flights.empty()) { // No flights available for this aircraft
214       valid = false;
215       return false;
216   }
217   
218   // Sort all the scheduled flights according to scheduled departure time.
219   // Because this is done at every update, we only need to check the status
220   // of the first listed flight. 
221   //sort(flights.begin(), flights.end(), compareScheduledFlights);
222   
223   if (firstRun) {
224      if (fgGetBool("/sim/traffic-manager/instantaneous-action") == true) {
225          deptime = now; // + rand() % 300; // Wait up to 5 minutes until traffic starts moving to prevent too many aircraft 
226                                    // from cluttering the gate areas.
227      }
228      firstRun = false;
229   }
230   
231   FGScheduledFlight* flight = flights.front();
232   if (!deptime) {
233     deptime = flight->getDepartureTime();
234     //cerr << "Settiing departure time " << deptime << endl;
235   }
236     
237   if (AIManagerRef) {
238     // Check if this aircraft has been released. 
239     FGTrafficManager *tmgr = (FGTrafficManager *) globals->get_subsystem("traffic-manager");
240     if (tmgr->isReleased(AIManagerRef)) {
241       AIManagerRef = 0;
242     } else {
243       return true; // in visual range, let the AIManager handle it
244     }
245   }
246   
247   // This flight entry is entirely in the past, do we need to 
248   // push it forward in time to the next scheduled departure. 
249   if (flight->getArrivalTime() < now) {
250     SG_LOG (SG_GENERAL, SG_BULK, "Traffic Manager:      Flight is in the Past");
251     // Don't just update: check whether we need to load a new leg. etc.
252     // This update occurs for distant aircraft, so we can update the current leg
253     // and detach it from the current list of aircraft. 
254     flight->update();
255     flights.erase(flights.begin()); // pop_front(), effectively
256     return true;
257   }
258   
259   FGAirport* dep = flight->getDepartureAirport();
260   FGAirport* arr = flight->getArrivalAirport();
261   if (!dep || !arr) {
262     return false;
263   }
264     
265   double speed = 450.0;
266   if (dep != arr) {
267     totalTimeEnroute = flight->getArrivalTime() - flight->getDepartureTime();
268     if (flight->getDepartureTime() < now) {
269       elapsedTimeEnroute   = now - flight->getDepartureTime();
270       //remainingTimeEnroute = totalTimeEnroute - elapsedTimeEnroute;
271       double x = elapsedTimeEnroute / (double) totalTimeEnroute;
272       
273     // current pos is based on great-circle course between departure/arrival,
274     // with percentage of distance travelled, based upon percentage of time
275     // enroute elapsed.
276       double course, az2, distanceM;
277       SGGeodesy::inverse(dep->geod(), arr->geod(), course, az2, distanceM);
278       double coveredDistance = distanceM * x;
279       
280       SGGeodesy::direct(dep->geod(), course, coveredDistance, position, az2);
281       
282       SG_LOG (SG_GENERAL, SG_BULK, "Traffic Manager:      Flight is in progress, %=" << x);
283       speed = ((distanceM - coveredDistance) * SG_METER_TO_NM) / 3600.0;
284     } else {
285     // not departed yet
286       //remainingTimeEnroute = totalTimeEnroute;
287       elapsedTimeEnroute = 0;
288       position = dep->geod();
289       SG_LOG (SG_GENERAL, SG_BULK, "Traffic Manager:      Flight is pending, departure in "
290         << flight->getDepartureTime() - now << " seconds ");
291     }
292   } else {
293     // departure / arrival coincident
294     //remainingTimeEnroute = totalTimeEnroute = 0.0;
295     elapsedTimeEnroute = 0;
296     position = dep->geod();
297   }
298     
299   // cartesian calculations are more numerically stable over the (potentially)
300   // large distances involved here: see bug #80
301   distanceToUser = dist(userCart, SGVec3d::fromGeod(position)) * SG_METER_TO_NM;
302
303   // If distance between user and simulated aircaft is less
304   // then 500nm, create this flight. At jet speeds 500 nm is roughly
305   // one hour flight time, so that would be a good approximate point
306   // to start a more detailed simulation of this aircraft.
307   SG_LOG (SG_GENERAL, SG_BULK, "Traffic manager: " << registration << " is scheduled for a flight from " 
308              << dep->getId() << " to " << arr->getId() << ". Current distance to user: " 
309              << distanceToUser);
310   if (distanceToUser >= TRAFFICTOAIDISTTOSTART) {
311     return true; // out of visual range, for the moment.
312   }
313   return createAIAircraft(flight, speed, deptime);
314 }
315
316 bool FGAISchedule::validModelPath(const std::string& modelPath)
317 {
318     SGPath mp(globals->get_fg_root());
319     SGPath mp_ai = mp;
320
321     mp.append(modelPath);
322     mp_ai.append("AI");
323     mp_ai.append(modelPath);
324
325     return mp.exists() || mp_ai.exists();
326 }
327
328 bool FGAISchedule::createAIAircraft(FGScheduledFlight* flight, double speedKnots, time_t deptime)
329 {
330   FGAirport* dep = flight->getDepartureAirport();
331   FGAirport* arr = flight->getArrivalAirport();
332   string flightPlanName = dep->getId() + "-" + arr->getId() + ".xml";
333   SG_LOG(SG_GENERAL, SG_INFO, "Traffic manager: Creating AIModel from:" << flightPlanName);
334
335   // Only allow traffic to be created when the model path (or the AI version of mp) exists
336   SGPath mp(globals->get_fg_root());
337   SGPath mp_ai = mp;
338
339   mp.append(modelPath);
340   mp_ai.append("AI");
341   mp_ai.append(modelPath);
342
343   if (!mp.exists() && !mp_ai.exists()) {
344     SG_LOG(SG_GENERAL, SG_WARN, "TrafficManager: Could not load model " << mp_ai.str());
345     return true;
346   }
347
348   FGAIAircraft *aircraft = new FGAIAircraft(this);
349   aircraft->setPerformance(acType, m_class); //"jet_transport";
350   aircraft->setCompany(airline); //i->getAirline();
351   aircraft->setAcType(acType); //i->getAcType();
352   aircraft->setPath(modelPath.c_str());
353   //aircraft->setFlightPlan(flightPlanName);
354   aircraft->setLatitude(position.getLatitudeDeg());
355   aircraft->setLongitude(position.getLongitudeDeg());
356   aircraft->setAltitude(flight->getCruiseAlt()*100); // convert from FL to feet
357   aircraft->setSpeed(0);
358   aircraft->setBank(0);
359       
360   courseToDest = SGGeodesy::courseDeg(position, arr->geod());
361   FGAIFlightPlan *fp = new FGAIFlightPlan(aircraft, flightPlanName, courseToDest, deptime,
362                                             dep, arr, true, radius, 
363                                             flight->getCruiseAlt()*100, 
364                                             position.getLatitudeDeg(), 
365                                             position.getLongitudeDeg(), 
366                                             speedKnots, flightType, acType, 
367                                             airline);
368   if (fp->isValidPlan()) {
369         aircraft->SetFlightPlan(fp);
370         FGAIManager* aimgr = (FGAIManager *) globals-> get_subsystem("ai-model");
371         aimgr->attach(aircraft);
372         AIManagerRef = aircraft->getID();
373         return true;
374   } else {
375         delete aircraft;
376         delete fp;
377         //hand back the flights that had already been scheduled
378         while (!flights.empty()) {
379             flights.front()->release();
380             flights.erase(flights.begin());
381         }
382         return false;
383   }
384 }
385
386 // Create an initial heading for user controlled aircraft.
387 void FGAISchedule::setHeading()  
388
389     courseToDest = SGGeodesy::courseDeg((*flights.begin())->getDepartureAirport()->geod(), (*flights.begin())->getArrivalAirport()->geod());
390 }
391
392 void FGAISchedule::scheduleFlights(time_t now)
393 {
394   if (!flights.empty()) {
395     return;
396   }
397   //string startingPort;
398   string userPort = fgGetString("/sim/presets/airport-id");
399   SG_LOG(SG_GENERAL, SG_BULK, "Scheduling Flights for : " << modelPath << " " <<  registration << " " << homePort);
400   FGScheduledFlight *flight = NULL;
401   do {
402     if (currentDestination.empty()) {
403         flight = findAvailableFlight(userPort, flightIdentifier, now, (now+6400));
404         if (!flight)
405             flight = findAvailableFlight(currentDestination, flightIdentifier);
406     } else {
407         flight = findAvailableFlight(currentDestination, flightIdentifier);
408     }
409     if (!flight) {
410       break;
411     }
412     currentDestination = flight->getArrivalAirport()->getId();
413     //cerr << "Current destination " <<  currentDestination << endl;
414     if (!initialized) {
415         string departurePort = flight->getDepartureAirport()->getId();
416         if (userPort == departurePort) {
417             lastRun = 1;
418             hits++;
419         } else {
420             lastRun = 0;
421         }
422         //runCount++;
423         initialized = true;
424     }
425   
426     time_t arr, dep;
427     dep = flight->getDepartureTime();
428     arr = flight->getArrivalTime();
429     string depT = asctime(gmtime(&dep));
430     string arrT = asctime(gmtime(&arr));
431
432     depT = depT.substr(0,24);
433     arrT = arrT.substr(0,24);
434     SG_LOG(SG_GENERAL, SG_BULK, "  Flight " << flight->getCallSign() << ":" 
435                              << "  "        << flight->getDepartureAirport()->getId() << ":"
436                              << "  "        << depT << ":"
437                              << " \""       << flight->getArrivalAirport()->getId() << "\"" << ":"
438                              << "  "        << arrT << ":");
439   
440     flights.push_back(flight);
441   } while (currentDestination != homePort);
442   SG_LOG(SG_GENERAL, SG_BULK, " Done ");
443 }
444
445 bool FGAISchedule::next()
446 {
447   if (!flights.empty()) {
448     flights.front()->release();
449     flights.erase(flights.begin());
450   }
451   
452   FGScheduledFlight *flight = findAvailableFlight(currentDestination, flightIdentifier);
453   if (!flight) {
454     return false;
455   }
456   
457   currentDestination = flight->getArrivalAirport()->getId();
458 /*
459   time_t arr, dep;
460   dep = flight->getDepartureTime();
461   arr = flight->getArrivalTime();
462   string depT = asctime(gmtime(&dep));
463   string arrT = asctime(gmtime(&arr));
464
465   depT = depT.substr(0,24);
466   arrT = arrT.substr(0,24);
467   //cerr << "  " << flight->getCallSign() << ":" 
468   //     << "  " << flight->getDepartureAirport()->getId() << ":"
469   //     << "  " << depT << ":"
470   //     << " \"" << flight->getArrivalAirport()->getId() << "\"" << ":"
471   //     << "  " << arrT << ":" << endl;
472 */
473    flights.push_back(flight);
474    return true;
475 }
476
477 FGScheduledFlight* FGAISchedule::findAvailableFlight (const string &currentDestination,
478                                                       const string &req,
479                                                      time_t min, time_t max)
480 {
481     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
482
483     FGTrafficManager *tmgr = (FGTrafficManager *) globals->get_subsystem("traffic-manager");
484     FGScheduledFlightVecIterator fltBegin, fltEnd;
485     fltBegin = tmgr->getFirstFlight(req);
486     fltEnd   = tmgr->getLastFlight(req);
487
488
489      //cerr << "Finding available flight " << endl;
490      // For Now:
491      // Traverse every registered flight
492      if (fltBegin == fltEnd) {
493           //cerr << "No Flights Scheduled for " << req << endl;
494      }
495      int counter = 0;
496      for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
497           (*i)->adjustTime(now);
498            //sort(fltBegin, fltEnd, compareScheduledFlights);
499            //cerr << counter++ << endl;
500      }
501      std::sort(fltBegin, fltEnd, compareScheduledFlights);
502      for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
503           //bool valid = true;
504           counter++;
505           if (!(*i)->isAvailable()) {
506                //cerr << (*i)->getCallSign() << "is no longer available" << endl;
507                continue;
508           }
509           if (!((*i)->getRequirement() == req)) {
510                continue;
511           }
512           if (!(((*i)->getArrivalAirport()) && ((*i)->getDepartureAirport()))) {
513               continue;
514           }
515           if (!(currentDestination.empty())) {
516               if (currentDestination != (*i)->getDepartureAirport()->getId()) {
517                    //cerr << (*i)->getCallSign() << "Doesn't match destination" << endl;
518                    //cerr << "Current Destination " << currentDestination << "Doesnt match flight's " <<
519                    //          (*i)->getArrivalAirport()->getId() << endl;
520                    continue;
521               }
522           }
523           if (flights.size()) {
524             time_t arrival = flights.back()->getArrivalTime();
525             int groundTime = groundTimeFromRadius();
526             if ((*i)->getDepartureTime() < (arrival+(groundTime)))
527                 continue;
528           }
529           if (min != 0) {
530               time_t dep = (*i)->getDepartureTime();
531               if ((dep < min) || (dep > max))
532                   continue;
533           }
534
535           // So, if we actually get here, we have a winner
536           //cerr << "found flight: " << req << " : " << currentDestination << " : " <<       
537           //         (*i)->getArrivalAirport()->getId() << endl;
538           (*i)->lock();
539           return (*i);
540      }
541      // matches req?
542      // if currentDestination has a value, does it match departure of next flight?
543      // is departure time later than planned arrival?
544      // is departure port valid?
545      // is arrival port valid?
546      //cerr << "Ack no flight found: " << endl;
547      return NULL;
548 }
549
550 int FGAISchedule::groundTimeFromRadius()
551 {
552     if (radius < 10) 
553         return 15 * 60;
554     else if (radius < 15)
555         return 20 * 60;
556     else if (radius < 20)
557         return 30 * 60;
558     else if (radius < 25)
559         return 50 * 60;
560     else if (radius < 30)
561         return 90 * 60;
562     else 
563         return 120 * 60;
564 }
565
566
567 double FGAISchedule::getSpeed()
568 {
569   FGScheduledFlightVecIterator i = flights.begin();
570  
571   FGAirport* dep = (*i)->getDepartureAirport(),
572    *arr = (*i)->getArrivalAirport();
573   double dist = SGGeodesy::distanceNm(dep->geod(), arr->geod());
574   double remainingTimeEnroute = (*i)->getArrivalTime() - (*i)->getDepartureTime();
575
576   double speed = dist / (remainingTimeEnroute/3600.0);
577   SG_CLAMP_RANGE(speed, 300.0, 500.0);
578   return speed;
579 }
580
581 void FGAISchedule::setScore   () 
582
583     if (runCount) {
584         score = ((double) hits / (double) runCount);
585     } else {
586         if (homePort == fgGetString("/sim/presets/airport-id")) {
587             score = 0.1;
588         } else {
589             score = 0.0;
590         }
591     }
592     runCount++;
593 }
594
595 bool compareSchedules(FGAISchedule*a, FGAISchedule*b)
596
597   return (*a) < (*b); 
598
599
600 bool FGAISchedule::operator< (const FGAISchedule &other) const
601
602     //cerr << "Sorting " << registration << " and "  << other.registration << endl;
603     double currentScore = score       * (1.5 - lastRun);
604     double otherScore   = other.score * (1.5 - other.lastRun);
605     return currentScore > otherScore;
606 }
607