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