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