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