1 /******************************************************************************
2  * TrafficMGr.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  * Traffic manager parses airlines timetable-like data and uses this to
24  * determine the approximate position of each AI aircraft in its database.
25  * When an AI aircraft is close to the user's position, a more detailed
26  * AIModels based simulation is set up.
27  *
28  * I'm currently assuming the following simplifications:
29  * 1) The earth is a perfect sphere
30  * 2) Each aircraft flies a perfect great circle route.
31  * 3) Each aircraft flies at a constant speed (with infinite accelerations and
32  *    decelerations)
33  * 4) Each aircraft leaves at exactly the departure time.
34  * 5) Each aircraft arrives at exactly the specified arrival time.
35  *
36  *
37  *****************************************************************************/
38 
39 #ifdef HAVE_CONFIG_H
40 #  include "config.h"
41 #endif
42 
43 #include <stdlib.h>
44 #include <time.h>
45 #include <cstring>
46 #include <iostream>
47 #include <fstream>
48 #include <mutex>
49 
50 
51 #include <string>
52 #include <vector>
53 #include <algorithm>
54 
55 #include <simgear/compiler.h>
56 #include <simgear/debug/ErrorReportingCallback.hxx>
57 #include <simgear/misc/sg_path.hxx>
58 #include <simgear/io/iostreams/sgstream.hxx>
59 #include <simgear/misc/sg_dir.hxx>
60 #include <simgear/props/props.hxx>
61 #include <simgear/structure/subsystem_mgr.hxx>
62 #include <simgear/structure/exception.hxx>
63 #include <simgear/threads/SGThread.hxx>
64 #include <simgear/timing/sg_time.hxx>
65 
66 #include <simgear/xml/easyxml.hxx>
67 #include <simgear/scene/tsync/terrasync.hxx>
68 
69 #include <AIModel/AIAircraft.hxx>
70 #include <AIModel/AIFlightPlan.hxx>
71 #include <AIModel/AIBase.hxx>
72 #include <AIModel/performancedb.hxx>
73 
74 #include <Airports/airport.hxx>
75 #include <Main/fg_init.hxx>
76 #include <Main/globals.hxx>
77 #include <Main/fg_props.hxx>
78 #include <Main/sentryIntegration.hxx>
79 
80 #include "TrafficMgr.hxx"
81 
82 using std::sort;
83 using std::strcmp;
84 using std::endl;
85 using std::string;
86 using std::vector;
87 
88 /**
89  * Thread encapsulating parsing the traffic schedules.
90  */
91 class ScheduleParseThread : public SGThread, public XMLVisitor
92 {
93 public:
ScheduleParseThread(FGTrafficManager * traffic)94   ScheduleParseThread(FGTrafficManager* traffic) :
95     _trafficManager(traffic),
96     _isFinished(false),
97     _cancelThread(false),
98     cruiseAlt(0),
99     score(0),
100     acCounter(0),
101     radius(0),
102     offset(0),
103     heavy(false)
104   {
105 
106   }
107 
108   // if we're destroyed while running, ensure the thread exits cleanly
~ScheduleParseThread()109   ~ScheduleParseThread()
110   {
111     _lock.lock();
112     if (!_isFinished) {
113       _cancelThread = true; // request cancellation so we don't wait ages
114       _lock.unlock();
115       join();
116     } else {
117       _lock.unlock();
118     }
119   }
120 
setTrafficDirs(const PathList & dirs)121   void setTrafficDirs(const PathList& dirs)
122   {
123     _trafficDirPaths = dirs;
124   }
125 
isFinished() const126   bool isFinished() const
127   {
128     std::lock_guard<std::mutex> g(_lock);
129     return _isFinished;
130   }
131 
run()132   void run() override
133   {
134       for (const auto& p : _trafficDirPaths) {
135           parseTrafficDir(p);
136           if (_cancelThread) {
137               return;
138           }
139       }
140 
141     std::lock_guard<std::mutex> g(_lock);
142     _isFinished = true;
143   }
144 
startXML()145     void startXML()
146     {
147         //cout << "Start XML" << endl;
148         requiredAircraft = "";
149         homePort = "";
150     }
151 
endXML()152     void endXML()
153     {
154         //cout << "End XML" << endl;
155     }
156 
startElement(const char * name,const XMLAttributes & atts)157     void startElement(const char *name,
158                                         const XMLAttributes & atts)
159     {
160         const char *attval;
161         //cout << "Start element " << name << endl;
162         //FGTrafficManager temp;
163         //for (int i = 0; i < atts.size(); i++)
164         //  if (string(atts.getName(i)) == string("include"))
165         attval = atts.getValue("include");
166         if (attval != 0) {
167             //cout << "including " << attval << endl;
168             SGPath path = globals->get_fg_root();
169             path.append("/Traffic/");
170             path.append(attval);
171             readXML(path, *this);
172         }
173         elementValueStack.push_back("");
174         //  cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl;
175     }
176 
endElement(const char * name)177     void endElement(const char *name)
178     {
179         //cout << "End element " << name << endl;
180         const string & value = elementValueStack.back();
181 
182         if (!strcmp(name, "model"))
183             mdl = value;
184         else if (!strcmp(name, "livery"))
185             livery = value;
186         else if (!strcmp(name, "home-port"))
187             homePort = value;
188         else if (!strcmp(name, "registration"))
189             registration = value;
190         else if (!strcmp(name, "airline"))
191             airline = value;
192         else if (!strcmp(name, "actype"))
193             acType = value;
194         else if (!strcmp(name, "required-aircraft"))
195             requiredAircraft = value;
196         else if (!strcmp(name, "flighttype"))
197             flighttype = value;
198         else if (!strcmp(name, "radius"))
199             radius = atoi(value.c_str());
200         else if (!strcmp(name, "offset"))
201             offset = atoi(value.c_str());
202         else if (!strcmp(name, "performance-class"))
203             m_class = value;
204         else if (!strcmp(name, "heavy")) {
205             if (value == string("true"))
206                 heavy = true;
207             else
208                 heavy = false;
209         } else if (!strcmp(name, "callsign"))
210             callsign = value;
211         else if (!strcmp(name, "fltrules"))
212             fltrules = value;
213         else if (!strcmp(name, "port"))
214             port = value;
215         else if (!strcmp(name, "time"))
216             timeString = value;
217         else if (!strcmp(name, "departure")) {
218             departurePort = port;
219             departureTime = timeString;
220         } else if (!strcmp(name, "cruise-alt"))
221             cruiseAlt = atoi(value.c_str());
222         else if (!strcmp(name, "arrival")) {
223             arrivalPort = port;
224             arrivalTime = timeString;
225         } else if (!strcmp(name, "repeat"))
226             repeat = value;
227         else if (!strcmp(name, "flight")) {
228             // We have loaded and parsed all the information belonging to this flight
229             // so we temporarily store it.
230             //cerr << "Pusing back flight " << callsign << endl;
231             //cerr << callsign  <<  " " << fltrules     << " "<< departurePort << " " <<  arrivalPort << " "
232             //   << cruiseAlt <<  " " << departureTime<< " "<< arrivalTime   << " " << repeat << endl;
233 
234             //Prioritize aircraft
235             string apt = fgGetString("/sim/presets/airport-id");
236             //cerr << "Airport information: " << apt << " " << departurePort << " " << arrivalPort << endl;
237             //if (departurePort == apt) score++;
238             //flights.push_back(new FGScheduledFlight(callsign,
239             //                                fltrules,
240             //                                departurePort,
241             //                                arrivalPort,
242             //                                cruiseAlt,
243             //                                departureTime,
244             //                                arrivalTime,
245             //                                repeat));
246             if (requiredAircraft == "") {
247                 char buffer[16];
248                 snprintf(buffer, 16, "%d", acCounter);
249                 requiredAircraft = buffer;
250             }
251             SG_LOG(SG_AI, SG_BULK, "Adding flight: " << callsign << " "
252                    << fltrules << " "
253                    << departurePort << " "
254                    << arrivalPort << " "
255                    << cruiseAlt << " "
256                    << departureTime << " "
257                    << arrivalTime << " " << repeat << " " << requiredAircraft);
258             // For database maintainance purposes, it may be convenient to
259             //
260             if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
261                 SG_LOG(SG_AI, SG_ALERT, "Traffic Dump FLIGHT," << callsign << ","
262                        << fltrules << ","
263                        << departurePort << ","
264                        << arrivalPort << ","
265                        << cruiseAlt << ","
266                        << departureTime << ","
267                        << arrivalTime << "," << repeat << "," << requiredAircraft);
268             }
269 
270             _trafficManager->flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
271                                                                       fltrules,
272                                                                       departurePort,
273                                                                       arrivalPort,
274                                                                       cruiseAlt,
275                                                                       departureTime,
276                                                                       arrivalTime,
277                                                                       repeat,
278                                                                       requiredAircraft));
279             requiredAircraft = "";
280         } else if (!strcmp(name, "aircraft")) {
281             endAircraft();
282         }
283 
284         elementValueStack.pop_back();
285     }
286 
287 
data(const char * s,int len)288     void data(const char *s, int len)
289     {
290         string token = string(s, len);
291         //cout << "Character data " << string(s,len) << endl;
292         elementValueStack.back() += token;
293     }
294 
pi(const char * target,const char * data)295     void pi(const char *target, const char *data)
296     {
297         //cout << "Processing instruction " << target << ' ' << data << endl;
298     }
299 
warning(const char * message,int line,int column)300     void warning(const char *message, int line, int column)
301     {
302         SG_LOG(SG_IO, SG_WARN,
303                "Warning: " << message << " (" << line << ',' << column << ')');
304     }
305 
error(const char * message,int line,int column)306     void error(const char *message, int line, int column)
307     {
308         SG_LOG(SG_IO, SG_ALERT,
309                "Error: " << message << " (" << line << ',' << column << ')');
310     }
311 
312 private:
endAircraft()313     void endAircraft()
314     {
315         string isHeavy = heavy ? "true" : "false";
316 
317         if (missingModels.find(mdl) != missingModels.end()) {
318             // don't stat() or warn again
319             requiredAircraft = homePort = "";
320             return;
321         }
322 
323         if (!FGAISchedule::validModelPath(mdl)) {
324             missingModels.insert(mdl);
325             simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AITrafficSchedule, "Missing traffic model path:" + mdl, _currentFile);
326             requiredAircraft = homePort = "";
327             return;
328         }
329 
330         int proportion =
331         (int) (fgGetDouble("/sim/traffic-manager/proportion") * 100);
332         int randval = rand() & 100;
333         if (randval > proportion) {
334             requiredAircraft = homePort = "";
335             return;
336         }
337 
338         if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
339             SG_LOG(SG_AI, SG_ALERT, "Traffic Dump AC," << homePort << "," << registration << "," << requiredAircraft
340                    << "," << acType << "," << livery << ","
341                    << airline << ","  << m_class << "," << offset << "," << radius << "," << flighttype << "," << isHeavy << "," << mdl);
342         }
343 
344         if (requiredAircraft == "") {
345             char buffer[16];
346             snprintf(buffer, 16, "%d", acCounter);
347             requiredAircraft = buffer;
348         }
349         if (homePort == "") {
350             homePort = departurePort;
351         }
352 
353         // caution, modifying the scheduled aircraft strucutre from the
354         // 'wrong' thread. This is safe becuase FGTrafficManager won't touch
355         // the structure while we exist.
356         _trafficManager->scheduledAircraft.push_back(new FGAISchedule(mdl,
357                                                      livery,
358                                                      homePort,
359                                                      registration,
360                                                      requiredAircraft,
361                                                      heavy,
362                                                      acType,
363                                                      airline,
364                                                      m_class,
365                                                      flighttype,
366                                                      radius, offset));
367 
368         acCounter++;
369         requiredAircraft = "";
370         homePort = "";
371         score = 0;
372     }
373 
parseTrafficDir(const SGPath & path)374     void parseTrafficDir(const SGPath& path)
375     {
376         SGTimeStamp st;
377         st.stamp();
378 
379         simgear::Dir trafficDir(path);
380         simgear::PathList d = trafficDir.children(simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
381 
382         for (const auto& p : d) {
383             simgear::Dir d2(p);
384             SG_LOG(SG_AI, SG_DEBUG, "parsing traffic in:" << p);
385             simgear::PathList trafficFiles = d2.children(simgear::Dir::TYPE_FILE, ".xml");
386             for (const auto& xml : trafficFiles) {
387                 _currentFile = xml;
388                 try {
389                     readXML(xml, *this);
390                     if (_cancelThread) {
391                         return;
392                     }
393                 } catch (sg_exception& e) {
394                     simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::AITrafficSchedule,
395                                            "XML errors parsinng traffic:" + e.getFormattedMessage(), xml);
396                 }
397             }
398         } // of sub-directories iteration
399 
400         SG_LOG(SG_AI, SG_INFO, "parsing traffic schedules took:" << st.elapsedMSec() << "msec");
401     }
402 
403   FGTrafficManager* _trafficManager;
404   mutable std::mutex _lock;
405   bool _isFinished;
406   bool _cancelThread;
407   simgear::PathList _trafficDirPaths;
408   SGPath _currentFile;
409 
410   // parser state
411 
412   string_list elementValueStack;
413   // record model paths which are missing, to avoid duplicate
414   // warnings when parsing traffic schedules.
415   std::set<std::string> missingModels;
416 
417   std::string mdl, livery, registration, callsign, fltrules,
418       port, timeString, departurePort, departureTime, arrivalPort, arrivalTime,
419       repeat, acType, airline, m_class, flighttype, requiredAircraft, homePort;
420   int cruiseAlt;
421   int score, acCounter;
422   double radius, offset;
423   bool heavy;
424 
425 };
426 
427 /******************************************************************************
428  * TrafficManager
429  *****************************************************************************/
FGTrafficManager()430 FGTrafficManager::FGTrafficManager() :
431   inited(false),
432   doingInit(false),
433   trafficSyncRequested(false),
434   waitingMetarTime(0.0),
435   enabled("/sim/traffic-manager/enabled"),
436   aiEnabled("/sim/ai/enabled"),
437   realWxEnabled("/environment/realwx/enabled"),
438   metarValid("/environment/metar/valid"),
439   active("/sim/traffic-manager/active"),
440   aiDataUpdateNow("/sim/terrasync/ai-data-update-now")
441 {
442 }
443 
~FGTrafficManager()444 FGTrafficManager::~FGTrafficManager()
445 {
446     shutdown();
447 }
448 
shutdown()449 void FGTrafficManager::shutdown()
450 {
451     if (!inited) {
452       if (doingInit) {
453         scheduleParser.reset();
454         doingInit = false;
455         active = false;
456       }
457 
458       return;
459     }
460 
461     // Save the heuristics data
462     bool saveData = false;
463     sg_ofstream cachefile;
464     if (fgGetBool("/sim/traffic-manager/heuristics")) {
465         SGPath cacheData(globals->get_fg_home());
466         cacheData.append("ai");
467         const string airport = fgGetString("/sim/presets/airport-id");
468 
469         if ((airport) != "") {
470             char buffer[128];
471             ::snprintf(buffer, 128, "%c/%c/%c/",
472                        airport[0], airport[1], airport[2]);
473             cacheData.append(buffer);
474             cacheData.append(airport + "-cache.txt");
475 
476             // Note: Intuitively, this doesn't make sense, but I do need to create the full file path first
477             // before creating the directories. The SimGear fgpath code has changed so that it first chops off
478             // the trailing dir separator and then determines the directory part of the file path by searching
479             // for the last dir separator. Effecively, this causes a full element of the directory tree to be
480             // skipped.
481             SG_LOG(SG_GENERAL, SG_DEBUG, "Trying to create dir for : " << cacheData);
482             if (!cacheData.exists()) {
483                 cacheData.create_dir(0755);
484             }
485             saveData = true;
486             cachefile.open(cacheData);
487             cachefile << "[TrafficManagerCachedata:ref:2011:09:04]" << endl;
488 
489         }
490     }
491 
492     for (auto acft : scheduledAircraft) {
493         if (saveData) {
494             cachefile << acft->getRegistration() << " "
495                 << acft->getRunCount() << " "
496                 << acft->getHits() << " "
497                 << acft->getLastUsed() << endl;
498         }
499         delete acft;
500     }
501     if (saveData) {
502         cachefile.close();
503     }
504     scheduledAircraft.clear();
505 
506     for (auto flight : flights) {
507         for (auto scheduled : flight.second)
508             delete scheduled;
509     }
510     flights.clear();
511 
512     currAircraft = scheduledAircraft.begin();
513     doingInit = false;
514     inited = false;
515     trafficSyncRequested = false;
516     active = false;
517 }
518 
doDataSync()519 bool FGTrafficManager::doDataSync()
520 {
521     auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
522     bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled");
523     if (doDataSync && terraSync) {
524         if (!trafficSyncRequested) {
525             SG_LOG(SG_AI, SG_INFO, "Sync of AI traffic via TerraSync enabled");
526             terraSync->scheduleDataDir("AI/Traffic");
527             trafficSyncRequested = true;
528         }
529 
530         if (terraSync->isDataDirPending("AI/Traffic")) {
531             return false; // remain in the init state
532         }
533 
534         trafficSyncRequested = false;
535         return true;
536 
537         SG_LOG(SG_AI, SG_INFO, "Traffic files sync complete");
538     }
539     return true;
540 }
541 
init()542 void FGTrafficManager::init()
543 {
544     if (!enabled) {
545       return;
546     }
547 
548     // TorstenD: don't start the traffic manager before the FDM is initialized
549     // The FDM needs the scenery loaded and will wait for our spawned AIModels PagedLOD Nodes
550     // to appear if they are close (less than 1000m) to our position
551     if( !fgGetBool("/sim/signals/fdm-initialized") )
552       return;
553 
554     assert(!doingInit);
555 
556     if (!doDataSync())
557         return; // remain in the init state whilst updating
558 
559     doingInit = true;
560     if (string(fgGetString("/sim/traffic-manager/datafile")).empty()) {
561         simgear::PathList dirs = globals->get_data_paths("AI/Traffic");
562 
563         // temporary flag to restrict loading while traffic data is found
564         // through terrasync /and/ fgdata. Ultimately we *do* want to be able to
565         // overlay sources.
566 
567         if (dirs.size() > 1) {
568             SGPath p = dirs.back();
569             if (simgear::strutils::starts_with(p.utf8Str(),
570                                                globals->get_fg_root().utf8Str()))
571             {
572                 dirs.pop_back();
573             }
574         }
575 
576         if (dirs.empty()) {
577             doingInit = false;
578             return;
579         }
580 
581         scheduleParser.reset(new ScheduleParseThread(this));
582         scheduleParser->setTrafficDirs(dirs);
583         scheduleParser->start();
584     } else {
585         fgSetBool("/sim/traffic-manager/heuristics", false);
586         SGPath path = string(fgGetString("/sim/traffic-manager/datafile"));
587         string ext = path.extension();
588         if (path.extension() == "xml") {
589             if (path.exists()) {
590                 // use a SchedulerParser to parse, but run it in this thread,
591                 // i.e don't start it
592                 ScheduleParseThread parser(this);
593                 readXML(path, parser);
594             }
595         } else if (path.extension() == "conf") {
596             if (path.exists()) {
597                 readTimeTableFromFile(path);
598             }
599         } else {
600              SG_LOG(SG_AI, SG_ALERT,
601                                "Unknown data format " << path
602                                 << " for traffic");
603         }
604         //exit(1);
605     }
606     active = true;
607 }
608 
609 
610 
finishInit()611 void FGTrafficManager::finishInit()
612 {
613     assert(doingInit);
614     SG_LOG(SG_AI, SG_INFO, "finishing AI-Traffic init");
615     loadHeuristics();
616     PerformanceDB* perfDB = globals->get_subsystem<PerformanceDB>();
617     // Do sorting and scoring separately, to take advantage of the "homeport" variable
618     for (auto schedule : scheduledAircraft) {
619         schedule->setScore();
620         if (!perfDB->havePerformanceDataForAircraftType(schedule->getAircraft())) {
621             SG_LOG(SG_AI, SG_DEV_WARN, "AI-Traffic: schedule aircraft missing performance data:" << schedule->getAircraft());
622         }
623     }
624 
625     sort(scheduledAircraft.begin(), scheduledAircraft.end(),
626          compareSchedules);
627     currAircraft = scheduledAircraft.begin();
628     currAircraftClosest = scheduledAircraft.begin();
629 
630     doingInit = false;
631     inited = true;
632     active = true;
633 }
634 
loadHeuristics()635 void FGTrafficManager::loadHeuristics()
636 {
637     if (!fgGetBool("/sim/traffic-manager/heuristics")) {
638         return;
639     }
640 
641     HeuristicMap heurMap;
642     //cerr << "Processing Heuristics" << endl;
643     // Load the heuristics data
644     SGPath cacheData(globals->get_fg_home());
645     cacheData.append("ai");
646     string airport = fgGetString("/sim/presets/airport-id");
647     if ((airport) != "") {
648       char buffer[128];
649       ::snprintf(buffer, 128, "%c/%c/%c/",
650                  airport[0], airport[1], airport[2]);
651       cacheData.append(buffer);
652       cacheData.append(airport + "-cache.txt");
653       string revisionStr;
654       if (cacheData.exists()) {
655         sg_ifstream data(cacheData);
656         data >> revisionStr;
657         if (revisionStr != "[TrafficManagerCachedata:ref:2011:09:04]") {
658           SG_LOG(SG_AI, SG_ALERT,"Traffic Manager Warning: discarding outdated cachefile " <<
659                  cacheData << " for Airport " << airport);
660         } else {
661           while (1) {
662             Heuristic h; // = new Heuristic;
663             data >> h.registration >> h.runCount >> h.hits >> h.lastRun;
664             if (data.eof())
665               break;
666             HeuristicMapIterator itr = heurMap.find(h.registration);
667             if (itr != heurMap.end()) {
668               SG_LOG(SG_AI, SG_DEV_WARN,"Traffic Manager Warning: found duplicate tailnumber " <<
669                      h.registration << " for AI aircraft");
670             } else {
671               heurMap[h.registration] = h;
672             }
673           }
674         }
675       }
676     }
677 
678   for(currAircraft = scheduledAircraft.begin(); currAircraft != scheduledAircraft.end(); ++currAircraft) {
679         const string& registration = (*currAircraft)->getRegistration();
680         HeuristicMapIterator itr = heurMap.find(registration);
681         if (itr != heurMap.end()) {
682             (*currAircraft)->setrunCount(itr->second.runCount);
683             (*currAircraft)->setHits(itr->second.hits);
684             (*currAircraft)->setLastUsed(itr->second.lastRun);
685         }
686     }
687 }
688 
metarReady(double dt)689 bool FGTrafficManager::metarReady(double dt)
690 {
691     // wait for valid METAR (when realWX is enabled only), since we need
692     // to know the active runway
693     if (metarValid || !realWxEnabled)
694     {
695         waitingMetarTime = 0.0;
696         return true;
697     }
698 
699     // METAR timeout: when running offline, remote server is down etc
700     if (waitingMetarStation != fgGetString("/environment/metar/station-id"))
701     {
702         // station has changed: wait for reply, restart timeout
703         waitingMetarTime = 0.0;
704         waitingMetarStation = fgGetString("/environment/metar/station-id");
705         return false;
706     }
707 
708     // timeout elapsed (10 seconds)?
709     if (waitingMetarTime > 20.0)
710     {
711         return true;
712     }
713 
714     waitingMetarTime += dt;
715     return false;
716 }
717 
update(double dt)718 void FGTrafficManager::update(double dt)
719 {
720     if (!enabled)
721     {
722         if (inited || doingInit)
723             shutdown();
724         return;
725     }
726 
727     if (!metarReady(dt))
728         return;
729 
730     if (aiDataUpdateNow)
731     {
732         aiDataUpdateNow = false;
733         shutdown();
734     }
735     if (!aiEnabled)
736     {
737         // traffic depends on AI module
738         aiEnabled = true;
739     }
740 
741     if (!inited) {
742         if (!doingInit) {
743             init();
744         }
745 
746         if (!doingInit || !scheduleParser->isFinished()) {
747           return;
748         }
749 
750         finishInit();
751     }
752 
753 
754     if (scheduledAircraft.empty()) {
755         return;
756     }
757 
758     SGVec3d userCart = globals->get_aircraft_position_cart();
759 
760     if (currAircraft == scheduledAircraft.end()) {
761         currAircraft = scheduledAircraft.begin();
762     }
763 
764     time_t now = globals->get_time_params()->get_cur_time();
765 
766     //cerr << "Processing << " << (*currAircraft)->getRegistration() << " with score " << (*currAircraft)->getScore() << endl;
767     if ((*currAircraft)->update(now, userCart)) {
768         // schedule is done - process another aircraft in next iteration
769         currAircraft++;
770     }
771 }
772 
readTimeTableFromFile(SGPath infileName)773 void FGTrafficManager::readTimeTableFromFile(SGPath infileName)
774 {
775     string model;
776     string livery;
777     string homePort;
778     string registration;
779     string flightReq;
780     bool   isHeavy;
781     string acType;
782     string airline;
783     string m_class;
784     string FlightType;
785     double radius;
786     double offset;
787 
788     char buffer[256];
789     string buffString;
790     vector <string> tokens, depTime,arrTime;
791     vector <string>::iterator it;
792 
793     sg_ifstream infile(infileName);
794     while (1) {
795          infile.getline(buffer, 256);
796          if (infile.eof()) {
797              break;
798          }
799          //cerr << "Read line : " << buffer << endl;
800          buffString = string(buffer);
801          tokens.clear();
802          Tokenize(buffString, tokens, " \t");
803          //for (it = tokens.begin(); it != tokens.end(); it++) {
804          //    cerr << "Tokens: " << *(it) << endl;
805          //}
806          //cerr << endl;
807          if (!tokens.empty()) {
808              if (tokens[0] == string("AC")) {
809                  if (tokens.size() != 13) {
810                      throw sg_io_exception("Error parsing traffic file @ " + buffString, infileName);
811                  }
812 
813 
814                  model          = tokens[12];
815                  livery         = tokens[6];
816                  homePort       = tokens[1];
817                  registration   = tokens[2];
818                  if (tokens[11] == string("false")) {
819                      isHeavy = false;
820                  } else {
821                      isHeavy = true;
822                  }
823                  acType         = tokens[4];
824                  airline        = tokens[5];
825                  flightReq      = tokens[3] + tokens[5];
826                  m_class        = tokens[10];
827                  FlightType     = tokens[9];
828                  radius         = atof(tokens[8].c_str());
829                  offset         = atof(tokens[7].c_str());;
830 
831                  if (!FGAISchedule::validModelPath(model)) {
832                      simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::AITrafficSchedule, "Missing traffic model path:" + model, infileName);
833                  } else {
834                      SG_LOG(SG_AI, SG_DEBUG, "Adding Aircraft" << model << " " << livery << " " << homePort << " " << registration << " " << flightReq << " " << isHeavy << " " << acType << " " << airline << " " << m_class << " " << FlightType << " " << radius << " " << offset);
835                      scheduledAircraft.push_back(new FGAISchedule(model,
836                                                                   livery,
837                                                                   homePort,
838                                                                   registration,
839                                                                   flightReq,
840                                                                   isHeavy,
841                                                                   acType,
842                                                                   airline,
843                                                                   m_class,
844                                                                   FlightType,
845                                                                   radius,
846                                                                   offset));
847                  } // of valid model path
848              }
849              if (tokens[0] == string("FLIGHT")) {
850                  //cerr << "Found flight " << buffString << " size is : " << tokens.size() << endl;
851                  if (tokens.size() != 10) {
852                      SG_LOG(SG_AI, SG_ALERT, "Error parsing traffic file " << infileName << " at " << buffString);
853                      exit(1);
854                  }
855                  string callsign = tokens[1];
856                  string fltrules = tokens[2];
857                  string weekdays = tokens[3];
858                  string departurePort = tokens[5];
859                  string arrivalPort   = tokens[7];
860                  int    cruiseAlt     = atoi(tokens[8].c_str());
861                  string depTimeGen    = tokens[4];
862                  string arrTimeGen    = tokens[6];
863                  string repeat        = "WEEK";
864                  string requiredAircraft = tokens[9];
865 
866                  if (weekdays.size() != 7) {
867                      SG_LOG(SG_AI, SG_ALERT, "Found misconfigured weekdays string" << weekdays);
868                      exit(1);
869                  }
870                  depTime.clear();
871                  arrTime.clear();
872                  Tokenize(depTimeGen, depTime, ":");
873                  Tokenize(arrTimeGen, arrTime, ":");
874                  double dep = atof(depTime[0].c_str()) + (atof(depTime[1].c_str()) / 60.0);
875                  double arr = atof(arrTime[0].c_str()) + (atof(arrTime[1].c_str()) / 60.0);
876                  //cerr << "Using " << dep << " " << arr << endl;
877                  bool arrivalWeekdayNeedsIncrement = false;
878                  if (arr < dep) {
879                        arrivalWeekdayNeedsIncrement = true;
880                  }
881                  for (int i = 0; i < 7; i++) {
882                      int j = i+1;
883                      if (weekdays[i] != '.') {
884                          char buffer[4];
885                          snprintf(buffer, 4, "%d/", j);
886                          string departureTime = string(buffer) + depTimeGen + string(":00");
887                          string arrivalTime;
888                          if (!arrivalWeekdayNeedsIncrement) {
889                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
890                          }
891                          if (arrivalWeekdayNeedsIncrement && i != 6 ) {
892                              snprintf(buffer, 4, "%d/", j+1);
893                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
894                          }
895                          if (arrivalWeekdayNeedsIncrement && i == 6 ) {
896                              snprintf(buffer, 4, "%d/", 0);
897                              arrivalTime   = string(buffer) + arrTimeGen  + string(":00");
898                          }
899                          SG_LOG(SG_AI, SG_ALERT, "Adding flight " << callsign       << " "
900                                                       << fltrules       << " "
901                                                       <<  departurePort << " "
902                                                       <<  arrivalPort   << " "
903                                                       <<  cruiseAlt     << " "
904                                                       <<  departureTime << " "
905                                                       <<  arrivalTime   << " "
906                                                       << repeat        << " "
907                                                       <<  requiredAircraft);
908 
909                          flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
910                                                                  fltrules,
911                                                                  departurePort,
912                                                                  arrivalPort,
913                                                                  cruiseAlt,
914                                                                  departureTime,
915                                                                  arrivalTime,
916                                                                  repeat,
917                                                                  requiredAircraft));
918                     }
919                 }
920              }
921          }
922 
923     }
924     //exit(1);
925 }
926 
927 
Tokenize(const string & str,vector<string> & tokens,const string & delimiters)928 void FGTrafficManager::Tokenize(const string& str,
929                       vector<string>& tokens,
930                       const string& delimiters)
931 {
932     // Skip delimiters at beginning.
933     string::size_type lastPos = str.find_first_not_of(delimiters, 0);
934     // Find first "non-delimiter".
935     string::size_type pos     = str.find_first_of(delimiters, lastPos);
936 
937     while (string::npos != pos || string::npos != lastPos)
938     {
939         // Found a token, add it to the vector.
940         tokens.push_back(str.substr(lastPos, pos - lastPos));
941         // Skip delimiters.  Note the "not_of"
942         lastPos = str.find_first_not_of(delimiters, pos);
943         // Find next "non-delimiter"
944         pos = str.find_first_of(delimiters, lastPos);
945     }
946 }
947 
948 
949 // Register the subsystem.
950 SGSubsystemMgr::Registrant<FGTrafficManager> registrantFGTrafficManager(
951     SGSubsystemMgr::POST_FDM,
952     {{"terrasync", SGSubsystemMgr::Dependency::HARD},
953      {"PerformanceDB", SGSubsystemMgr::Dependency::HARD}});
954