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