]> git.mxchange.org Git - flightgear.git/blob - src/Traffic/Schedule.cxx
Support disabling AI traffic at run-time.
[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     // 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::createAIAircraft(FGScheduledFlight* flight, double speedKnots, time_t deptime)
318 {
319   FGAirport* dep = flight->getDepartureAirport();
320   FGAirport* arr = flight->getArrivalAirport();
321   string flightPlanName = dep->getId() + "-" + arr->getId() + ".xml";
322   SG_LOG(SG_GENERAL, SG_INFO, "Traffic manager: Creating AIModel from:" << flightPlanName);
323
324   // Only allow traffic to be created when the model path (or the AI version of mp) exists
325   SGPath mp(globals->get_fg_root());
326   SGPath mp_ai = mp;
327
328   mp.append(modelPath);
329   mp_ai.append("AI");
330   mp_ai.append(modelPath);
331
332   if (!mp.exists() && !mp_ai.exists()) {
333     SG_LOG(SG_GENERAL, SG_WARN, "TrafficManager: Could not load model " << mp.str());
334     return true;
335   }
336
337   FGAIAircraft *aircraft = new FGAIAircraft(this);
338   aircraft->setPerformance(m_class); //"jet_transport";
339   aircraft->setCompany(airline); //i->getAirline();
340   aircraft->setAcType(acType); //i->getAcType();
341   aircraft->setPath(modelPath.c_str());
342   //aircraft->setFlightPlan(flightPlanName);
343   aircraft->setLatitude(position.getLatitudeDeg());
344   aircraft->setLongitude(position.getLongitudeDeg());
345   aircraft->setAltitude(flight->getCruiseAlt()*100); // convert from FL to feet
346   aircraft->setSpeed(0);
347   aircraft->setBank(0);
348       
349   courseToDest = SGGeodesy::courseDeg(position, arr->geod());
350     FGAIFlightPlan *fp = new FGAIFlightPlan(aircraft, flightPlanName, courseToDest, deptime, 
351                                             dep, arr, true, radius, 
352                                             flight->getCruiseAlt()*100, 
353                                             position.getLatitudeDeg(), 
354                                             position.getLongitudeDeg(), 
355                                             speedKnots, flightType, acType, 
356                                             airline);
357   if (fp->isValidPlan()) {
358         aircraft->SetFlightPlan(fp);
359         FGAIManager* aimgr = (FGAIManager *) globals-> get_subsystem("ai-model");
360         aimgr->attach(aircraft);
361         AIManagerRef = aircraft->getID();
362         return true;
363   } else {
364         delete aircraft;
365         delete fp;
366         //hand back the flights that had already been scheduled
367         while (!flights.empty()) {
368             flights.front()->release();
369             flights.erase(flights.begin());
370         }
371         return false;
372   }
373 }
374
375 // Create an initial heading for user controlled aircraft.
376 void FGAISchedule::setHeading()  
377
378     courseToDest = SGGeodesy::courseDeg((*flights.begin())->getDepartureAirport()->geod(), (*flights.begin())->getArrivalAirport()->geod());
379 }
380
381 void FGAISchedule::scheduleFlights(time_t now)
382 {
383   if (!flights.empty()) {
384     return;
385   }
386   //string startingPort;
387   string userPort = fgGetString("/sim/presets/airport-id");
388   SG_LOG(SG_GENERAL, SG_BULK, "Scheduling Flights for : " << modelPath << " " <<  registration << " " << homePort);
389   FGScheduledFlight *flight = NULL;
390   do {
391     if (currentDestination.empty()) {
392         flight = findAvailableFlight(userPort, flightIdentifier, now, (now+6400));
393         if (!flight)
394             flight = findAvailableFlight(currentDestination, flightIdentifier);
395     } else {
396         flight = findAvailableFlight(currentDestination, flightIdentifier);
397     }
398     if (!flight) {
399       break;
400     }
401     currentDestination = flight->getArrivalAirport()->getId();
402     //cerr << "Current destination " <<  currentDestination << endl;
403     if (!initialized) {
404         string departurePort = flight->getDepartureAirport()->getId();
405         if (userPort == departurePort) {
406             lastRun = 1;
407             hits++;
408         } else {
409             lastRun = 0;
410         }
411         //runCount++;
412         initialized = true;
413     }
414   
415     time_t arr, dep;
416     dep = flight->getDepartureTime();
417     arr = flight->getArrivalTime();
418     string depT = asctime(gmtime(&dep));
419     string arrT = asctime(gmtime(&arr));
420
421     depT = depT.substr(0,24);
422     arrT = arrT.substr(0,24);
423     SG_LOG(SG_GENERAL, SG_BULK, "  Flight " << flight->getCallSign() << ":" 
424                              << "  "        << flight->getDepartureAirport()->getId() << ":"
425                              << "  "        << depT << ":"
426                              << " \""       << flight->getArrivalAirport()->getId() << "\"" << ":"
427                              << "  "        << arrT << ":");
428   
429     flights.push_back(flight);
430   } while (currentDestination != homePort);
431   SG_LOG(SG_GENERAL, SG_BULK, " Done ");
432 }
433
434 bool FGAISchedule::next()
435 {
436   if (!flights.empty()) {
437     flights.front()->release();
438     flights.erase(flights.begin());
439   }
440   
441   FGScheduledFlight *flight = findAvailableFlight(currentDestination, flightIdentifier);
442   if (!flight) {
443     return false;
444   }
445   
446   currentDestination = flight->getArrivalAirport()->getId();
447 /*
448   time_t arr, dep;
449   dep = flight->getDepartureTime();
450   arr = flight->getArrivalTime();
451   string depT = asctime(gmtime(&dep));
452   string arrT = asctime(gmtime(&arr));
453
454   depT = depT.substr(0,24);
455   arrT = arrT.substr(0,24);
456   //cerr << "  " << flight->getCallSign() << ":" 
457   //     << "  " << flight->getDepartureAirport()->getId() << ":"
458   //     << "  " << depT << ":"
459   //     << " \"" << flight->getArrivalAirport()->getId() << "\"" << ":"
460   //     << "  " << arrT << ":" << endl;
461 */
462    flights.push_back(flight);
463    return true;
464 }
465
466 FGScheduledFlight* FGAISchedule::findAvailableFlight (const string &currentDestination,
467                                                       const string &req,
468                                                      time_t min, time_t max)
469 {
470     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
471
472     FGTrafficManager *tmgr = (FGTrafficManager *) globals->get_subsystem("traffic-manager");
473     FGScheduledFlightVecIterator fltBegin, fltEnd;
474     fltBegin = tmgr->getFirstFlight(req);
475     fltEnd   = tmgr->getLastFlight(req);
476
477
478      //cerr << "Finding available flight " << endl;
479      // For Now:
480      // Traverse every registered flight
481      if (fltBegin == fltEnd) {
482           //cerr << "No Flights Scheduled for " << req << endl;
483      }
484      int counter = 0;
485      for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
486           (*i)->adjustTime(now);
487            //sort(fltBegin, fltEnd, compareScheduledFlights);
488            //cerr << counter++ << endl;
489      }
490      std::sort(fltBegin, fltEnd, compareScheduledFlights);
491      for (FGScheduledFlightVecIterator i = fltBegin; i != fltEnd; i++) {
492           //bool valid = true;
493           counter++;
494           if (!(*i)->isAvailable()) {
495                //cerr << (*i)->getCallSign() << "is no longer available" << endl;
496                continue;
497           }
498           if (!((*i)->getRequirement() == req)) {
499                continue;
500           }
501           if (!(((*i)->getArrivalAirport()) && ((*i)->getDepartureAirport()))) {
502               continue;
503           }
504           if (!(currentDestination.empty())) {
505               if (currentDestination != (*i)->getDepartureAirport()->getId()) {
506                    //cerr << (*i)->getCallSign() << "Doesn't match destination" << endl;
507                    //cerr << "Current Destination " << currentDestination << "Doesnt match flight's " <<
508                    //          (*i)->getArrivalAirport()->getId() << endl;
509                    continue;
510               }
511           }
512           if (flights.size()) {
513             time_t arrival = flights.back()->getArrivalTime();
514             int groundTime = groundTimeFromRadius();
515             if ((*i)->getDepartureTime() < (arrival+(groundTime)))
516                 continue;
517           }
518           if (min != 0) {
519               time_t dep = (*i)->getDepartureTime();
520               if ((dep < min) || (dep > max))
521                   continue;
522           }
523
524           // So, if we actually get here, we have a winner
525           //cerr << "found flight: " << req << " : " << currentDestination << " : " <<       
526           //         (*i)->getArrivalAirport()->getId() << endl;
527           (*i)->lock();
528           return (*i);
529      }
530      // matches req?
531      // if currentDestination has a value, does it match departure of next flight?
532      // is departure time later than planned arrival?
533      // is departure port valid?
534      // is arrival port valid?
535      //cerr << "Ack no flight found: " << endl;
536      return NULL;
537 }
538
539 int FGAISchedule::groundTimeFromRadius()
540 {
541     if (radius < 10) 
542         return 15 * 60;
543     else if (radius < 15)
544         return 20 * 60;
545     else if (radius < 20)
546         return 30 * 60;
547     else if (radius < 25)
548         return 50 * 60;
549     else if (radius < 30)
550         return 90 * 60;
551     else 
552         return 120 * 60;
553 }
554
555
556 double FGAISchedule::getSpeed()
557 {
558   FGScheduledFlightVecIterator i = flights.begin();
559  
560   FGAirport* dep = (*i)->getDepartureAirport(),
561    *arr = (*i)->getArrivalAirport();
562   double dist = SGGeodesy::distanceNm(dep->geod(), arr->geod());
563   double remainingTimeEnroute = (*i)->getArrivalTime() - (*i)->getDepartureTime();
564
565   double speed = dist / (remainingTimeEnroute/3600.0);
566   SG_CLAMP_RANGE(speed, 300.0, 500.0);
567   return speed;
568 }
569
570 void FGAISchedule::setScore   () 
571
572     if (runCount) {
573         score = ((double) hits / (double) runCount);
574     } else {
575         if (homePort == fgGetString("/sim/presets/airport-id")) {
576             score = 0.1;
577         } else {
578             score = 0.0;
579         }
580     }
581     runCount++;
582 }
583
584 bool compareSchedules(FGAISchedule*a, FGAISchedule*b)
585
586   return (*a) < (*b); 
587
588
589 bool FGAISchedule::operator< (const FGAISchedule &other) const
590
591     //cerr << "Sorting " << registration << " and "  << other.registration << endl;
592     double currentScore = score       * (1.5 - lastRun);
593     double otherScore   = other.score * (1.5 - other.lastRun);
594     return currentScore > otherScore;
595 }
596